A key strength of Dependency Injection is that unit testing with DI supposed to be easy by switching components with their mock representatives.
To be able to successfully mock a dependency is not hard, but it still required a few steps that may take a couple hours to get right. Below is a tutorial on how to mock Dependency Injected classes.
Assuming you already have DI up and running in your main project, you will have a similar class:
//AppModule.kt
@Module
class AppModule (...) {
@Provides
@Singleton
fun provideSomething() = Something()
...
}
//AppComponent.kt
@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {
fun inject(target: SomeClass)
...
}
//YourApplication.kt
...
override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent.builder()
.appModule(AppModule())
.build()
...
In your test source (whether it is unit tests or instrumented tests), create a mock module class:
//MockAppModule.kt
@Module
class MockAppModule (...) {
@Provides
@Singleton
fun provideSomething(): Something = mockk(relaxed = true)
//This example use mockk, but you can replace it with any
//other mocking library, or even a mock subclass.
...
}
and the mock components class:
//MockComponent
@Singleton
@Component(modules = [TestAppModule::class])
interface MockComponent : AppComponent {
fun getSomething(): Something
//This declaration is only required to access mocked
//component from inside your test.
...
}
Now, to the main course, we will need to set up a test rule that would initiate your application class, and then switch the real components with the mocked ones:
class MockComponentRule() : TestRule {
private val mMockComponent: MockComponent
override fun apply(base: Statement, description: Description?): Statement {
return object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
val application: YourApplication = context.applicationContext as YourApplication
// Set the MockComponent before the test runs
application.appComponent = mMockComponent
// Run tests
base.evaluate()
}
}
}
init {
val application = context.applicationContext as YourApplication
mMockComponent = DaggerMockComponent.builder()
.mockAppModule(MockAppModule())
.build()
}
//And finally, to enable test to access mocked component
fun getMockSomething() = mMockComponent.getSomething()
}
Inside one of your test, declare the test rule:
class SomeFragmentTest {
private val component = MockComponentRule(
InstrumentationRegistry.getInstrumentation().targetContext)
private val main: ActivityTestRule<MainActivity> =
ActivityTestRule(MainActivity::class.java,
false, false)
@Rule
@JvmField
var chain: TestRule = RuleChain.outerRule(component).around(main)
...
Enjoy testing.