Skip to main content

Android Instrumented Testing

Override production modules in a custom Application class

Unlike unit tests, where you effectively call start Koin in each test class (i.e. startKoin or KoinTestExtension), in Instrumented tests Koin is started by your Application class.

For overriding production Koin modules, loadModules and unloadModules are often unsafe because the changes are not applied immediately. Instead, the recommended approach is to add a module of your overrides to modules used by startKoin in the Application class. If you want to keep the class that extends Application of your application untouched, you can create another one inside the AndroidTest package like:

class TestApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
modules(productionModule, instrumentedTestModule)
}
}
}

In order to use this custom Application in yours Instrumentation tests you may need to create a custom AndroidJUnitRunner like:

class InstrumentationTestRunner : AndroidJUnitRunner() {
override fun newApplication(
classLoader: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(classLoader, TestApplication::class.java.name, context)
}
}

And then register it inside your gradle file with:

testInstrumentationRunner "com.example.myapplication.InstrumentationTestRunner"

Override production modules with a test rule

If you want more flexibility, you still have to create the custom AndroidJUnitRunner but instead of having startKoin { ... } inside the custom application, you can put it inside a custom test rule like:

class KoinTestRule(
private val modules: List<Module>
) : TestWatcher() {
override fun starting(description: Description) {

if (getKoinApplicationOrNull() == null) {
startKoin {
androidContext(InstrumentationRegistry.getInstrumentation().targetContext.applicationContext)
modules(modules)
}
} else {
loadKoinModules(modules)
}
}

override fun finished(description: Description) {
unloadKoinModules(modules)
}
}

In this way we can potentially override the definitions directly from our test classes, like:

private val instrumentedTestModule = module {
factory<Something> { FakeSomething() }
}

@get:Rule
val koinTestRule = KoinTestRule(
modules = listOf(productionModule, instrumentedTestModule)
)