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.