Android Kotlin: Leveraging Mockito and Dagger 2 for Mocking and Dependency Injection in Integration/UI Tests

Wai Hein
7 min readMar 4, 2020

--

In this article, I will guide you through the process of setting up Dagger 2 for dependency injection and demonstrate how to mock dependencies using Mockito in your Android integration/UI tests. It is assumed that you already possess a certain level of familiarity with Dagger 2 and Mockito.

Let’s begin by installing the required dependencies in the Gradle files. Open the Gradle file of your app module and include the following dependencies:

implementation “com.google.dagger:dagger:$daggerVersion”
kapt “com.google.dagger:dagger-compiler:$daggerVersion”
kapt “com.google.dagger:dagger-android-processor:$daggerVersion”
kaptTest “com.google.dagger:dagger-compiler:$daggerVersion”
androidTestUtil ‘androidx.test:orchestrator:1.2.0’
testImplementation ‘junit:junit:4.12’
testImplementation ‘androidx.test:core:1.2.0’
testImplementation ‘org.mockito:mockito-core:2.7.22’
androidTestImplementation ‘junit:junit:4.12’
androidTestImplementation “androidx.test.espresso:espresso-core:$expressoVersion”
androidTestImplementation “androidx.test.espresso:espresso-intents:$expressoVersion”
androidTestImplementation ‘androidx.test:runner:1.2.0’
androidTestImplementation ‘androidx.test:rules:1.2.0’
androidTestImplementation ‘androidx.test.uiautomator:uiautomator:2.2.0’
androidTestImplementation ‘com.android.support.test:runner:1.1.1’
androidTestImplementation ‘org.mockito:mockito-android:2.7.22’

In the provided sample code snippet, certain dependencies such as Espresso and UI Automator may not be essential for every project. They are included here because they are utilized in the author’s specific project. Please examine the dependencies and eliminate any that are not necessary for your particular project setup.

Additionally, remember to adjust the testInstrumentationRunner in your app module’s build.gradle file to utilize the custom class. Here’s how you can accomplish this:

android {     compileSdkVersion 29     buildToolsVersion “29.0.2”     defaultConfig {         applicationId “your.package.name”         minSdkVersion 21         targetSdkVersion 29         versionCode 1         versionName “1.0”         // testInstrumentationRunner                  “androidx.test.runner.AndroidJUnitRunner”        testInstrumentationRunner “your.package.name.MockTestRunner”}//the rest of the code}

We are creating a custom MockTestRunner class because it allows us to inject our own custom application class for tests, which differs from the actual application class.

Here is the implementation of the MockTestRunner:

import android.app.Applicationimport android.content.Contextimport androidx.test.runner.AndroidJUnitRunnerimport your.package.name.MockApplicationControllerclass MockTestRunner: AndroidJUnitRunner(){      override fun newApplication(      cl: ClassLoader?,      className: String?,      context: Context?      ): Application {             return super.newApplication(cl, MockApplicationController::class.java!!.getName(), context)      }}

As observed, we are injecting our custom MockApplicationController application class, specially designed for testing purposes.

Firstly, create a class named ApplicationController extending from the Application class. Subsequently, create another class named MockApplicationController extending from the previously created ApplicationController class. I will delve into the implementation of these classes later.

First, I will guide you through the configuration/setup of Dagger 2 for dependency injection.

Configuring Dagger 2 for Dependency Injection

Initially, create a class named AppModule with the following code.

import android.app.Application
import android.content.Context
import your.package.name.services.*
import dagger.Module
import dagger.Provides
import javax.inject.Singleton

@Module
open class AppModule (private val app: Application) {

@Singleton
@Provides
fun provideContext(): Context = app

@Singleton
@Provides
open fun userService(): IUserService {
return ConcreteUserService()
}
}

In the provided code, the IUserService interface serves as the interface for your service class. Two classes will extend this interface: one is the concrete class containing the actual implementation, and the other is the mock version utilized in tests.

Next, you’ll need to create a Dagger app component interface. Create an interface named AppComponent with the following code.

import dagger.Component
import javax.inject.Singleton

@Singleton
@Component(modules = [ AppModule::class ])
interface AppComponent
{
fun inject(app: ApplicationController)

fun inject(app: MockApplicationController)
}

As evident in the code, there are two methods with the same name but different arguments. These methods are employed for injecting dependencies. The mock classes will be injected into the MockApplicationController class, while the concrete classes are injected into the ApplicationController class.

Next, you’ll also need to create an app module class for the tests. Create a class named TestAppModule with the following code.

import android.app.Application
import android.content.Context
import your.package.name.services.*
import dagger.Module
import dagger.Provides
import javax.inject.Singleton

@Module
open class TestAppModule (private val app: Application) {

@Singleton
@Provides
fun provideContext(): Context = app

@Singleton
@Provides
open fun userService(): IUserService {
return MockAuthService()
}
}

As observed, the code closely resembles the implementation of the AppModule class. The only distinction is that the userService() method now returns an instance of the MockAuthService class, as this class will be utilized for the tests.

You also need to create an interface named TestAppComponent with the following code, which will be utilized in the tests.

import dagger.Component
import javax.inject.Singleton

@Singleton
@Component(modules = [ TestAppModule::class ])
interface TestAppComponent: AppComponent
{
}

Now, all the necessary classes for configuring Dagger have been created.

Step 2:

The next step involves implementing dependency injection. Instead of injecting dependencies (such as service classes or repositories) directly into individual activity or fragment classes, we will inject them into our custom Application classes (ApplicationController and MockApplicationController). Subsequently, we can use these dependencies throughout the entire application by accessing them through the custom application classes. I trust that you grasp the concept to some extent.

Initially, we need to provide the implementation for the previously created ApplicationController class with the following code.

open class ApplicationController: Application()
{
lateinit var appComponent: AppComponent
protected open fun initDagger(app: ApplicationController): AppComponent {
return DaggerAppComponent
.builder()
.appModule(AppModule(app)).build()
}
@Inject
lateinit var userService: IUserService

override fun onCreate() {
super.onCreate()
appComponent = initDagger(this)
instance = this
this.appComponent.inject(this)
}
}

Let me elucidate the functionality of the ApplicationController class. In this class, we have a method named initDagger responsible for initializing the Dagger app component. This method is then invoked within the onCreate event of the ApplicationController class. Following the initialization, we proceed to inject the dependency—in our case, the IUserService. If you revisit the AppComponent class, you'll notice two inject methods: one for the ApplicationController and the other for the MockApplicationController. In this instance, we are passing this to the inject method, signifying the instance of the ApplicationController class. Consequently, we inject the dependency into the ApplicationController class.

For the ApplicationController class, the AppModule class is utilized. Examining the definition of the userService method in AppModule, you'll find it returns an instance of the ConcreteUserService class. This instance is then injected into the userService property of the ApplicationController class. Now, we have successfully configured Dagger for the actual application. In the subsequent step, we will configure Dagger 2 for dependency injection in tests.

We need to furnish the implementation for the MockApplicationController class with the following code.

class MockApplicationController: ApplicationController()
{
override fun initDagger(app: ApplicationController): AppComponent {
return DaggerTestAppComponent
.builder()
.testAppModule(TestAppModule(app))
.build()
}
}

Essentially, what we are doing is overriding the initDagger method to use classes specifically designed for tests. When the method to initialize Dagger is invoked with this as the parameter, it passes the instance of the MockApplicationController class. Consequently, it employs the inject method that has MockApplicationController as a parameter. Additionally, it utilizes TestAppModule over AppModule. As evident in the TestAppModule class, the userService method returns the MockAuthService instance. The concept here is to provide the mock implementation for tests in this class.

How does Dagger 2 determine the correct userService method to invoke and inject the appropriate object?

As evident in the ApplicationController class, we annotated the userService property with the @Inject annotation, indicating that the dependency will be injected into that field. Both the ConcreteUserService class and the MockUserService class implement the IUserService interface. Therefore, we specify the type as the IUserService interface. Dagger calls the methods in the AppModule class and the TestAppModule class, aligning the return type of the method with the field type.

For instance, in the ApplicationController class, it utilizes the AppModule class. Since the userService property is declared with the type IUserService, Dagger looks for the method that returns a type compatible with the IUserService interface. In the case of the AppModule class, it is the userService method, which returns an instance of the ConcreteUserService class. For TestAppModule, it would be the method returning an instance of the MockUserService class.

Up to this point, we have successfully configured and set up Dagger 2.

Now, for your tests, you can implement the mock logic in the MockUserService class.

You will utilize the instance of the userService field from the ApplicationController class within your activity or fragment to make API calls or perform actions as needed. If you declare the activity rule within your test class as shown below and launch the activity, the MockUserService class will be injected, thereby overriding the behavior of the ConcreteUserService class.

@get:Rule
var mainActivityRule: ActivityTestRule<MainActivity> = ActivityTestRule<MainActivity>(MainActivity::class.java, true, false)

You can launch the activity in your test using the following approach:

this.mainActivityRule.launchActivity(intent)

What if we aim to inject Mockito mock objects instead?

At times, we may wish to substitute the Mock classes with Mockito mock objects. To achieve this, we need to make slight adjustments to the TestAppModule class.

The updated version of the TestAppModule class is as follows:

@Module
open class TestAppModule (private val app: Application) {
private lateinit var userService: IUserService fun setUserService(userService: IUserService) {
this.userService = userService
} @Singleton
@Provides
fun provideContext(): Context = app
@Singleton
@Provides
open fun userService(): IUserService {
if (::userService.isInitialized) return this.userService return MockAuthService()
}
}

Essentially, what we’ve done is provide a mechanism to set a custom mock object. Additionally, you’ll need to include some extra code to utilize the Mockito mock object.

To use Mockito, the initial step is to declare a property within the test class adorned with the @Mock annotation.

@Mock
private lateinit var userServiceMock: IUserService

Next, we need to override the logic for initializing the Dagger app component for the test. Typically, the @Before method would be a suitable place to include this logic. The following code illustrates this.

@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
val instrumentation = InstrumentationRegistry.getInstrumentation()
val app = instrumentation.targetContext.applicationContext as MockApplicationController
var testModule = TestAppModule(app)
testModule.setUserService(this.userServiceMock)
app.appComponent = DaggerTestAppComponent
.builder()
.testAppModule(testModule)
.build()
app.appComponent.inject(app)
}

As observed, we are now injecting using the Mockito mock object placed into the TestAppModule object. Consequently, we can apply any assertions or methods provided by Mockito to the object within the test.

That concludes the article. I hope you find it helpful.

If you want to reach out to me, https://www.linkedin.com/in/wai-yan-hein-b99162123/.

--

--

Wai Hein
Wai Hein

Written by Wai Hein

8 x AWS Certified Full-stack | DevOps | Serverless Engineer with over a decade of experience

Responses (1)