Skip to main content

Verifying your Koin configuration

note

Koin allows you to verify your configuration modules, avoiding to discover dependency injection issues at runtime.

Koin Configuration check with Verify() - JVM Only [3.3]

Use the verify() extension function on a Koin Module. That's it! Under the hood, This will verify all constructor classes and crosscheck with the Koin configuration to know if there is a component declared for this dependency. In case of failure, the function will throw a MissingKoinDefinitionException.

val niaAppModule = module {
includes(
jankStatsKoinModule,
dataKoinModule,
syncWorkerKoinModule,
topicKoinModule,
authorKoinModule,
interestsKoinModule,
settingsKoinModule,
bookMarksKoinModule,
forYouKoinModule
)
viewModelOf(::MainActivityViewModel)
}
class NiaAppModuleCheck {

@Test
fun checkKoinModule() {

// Verify Koin configuration
niaAppModule.verify(
// List types used in definitions but not declared directly (like parameters injection)
extraTypes = listOf(...)
)
}
}

Launch the JUnit test and you're done! ✅

As you may see, we use the extraTypesparameter to list types used in the Koin configuration but not declared directly. This is the case for SavedStateHandle and WorkerParameters types, that are used as injected parameters. The Context is declared by androidContext() function at start.

The verify() API is ultra light to run and doesn't require any kind of mock/stubb to run on your configuration.

Koin Dynamic Check - CheckModules()

Invoke the checkModules() function within a simple JUnit test. This will launch your modules and try to run each possible definition for you.

class CheckModulesTest : KoinTest {

@Test
fun verifyKoinApp() {

koinApplication {
modules(module1,module2)
checkModules()
}
}
}

also possible to use checkKoinModules:

class CheckModulesTest : KoinTest {

@Test
fun verifyKoinApp() {

checkKoinModules(listOf(module1,module2))
}
}

CheckModule DSL

For any definition that is using injected parameters, properties or dynamic instances, the checkModules DSL allows to specify how to work with the following case:

  • withInstance(value) - will add value instance to Koin graph (can be used in dependency or parameter)

  • withInstance<MyType>() - will add a mocked instance of MyType. Use MockProviderRule. (can be used in dependency or parameter)

  • withParameter<Type>(qualifier){ qualifier -> value } - will add value instance to be injected as parameter

  • withParameter<Type>(qualifier){ qualifier -> parametersOf(...) } - will add value instance to be injected as parameter

  • withProperty(key,value) - add property to Koin

Allow mocking with a Junit rule

To use mocks with checkModules, you need to provide a MockProviderRule

@get:Rule
val mockProvider = MockProviderRule.create { clazz ->
// Mock with your framework here given clazz
}

Verifying modules with dynamic behavior (3.1.3+)

To verify a dynamic behavior like following, let's use the CheckKoinModules DSL to provide the missing instance data to our test:

val myModule = module {
factory { (id: String) -> FactoryPresenter(id) }
}

You can verify it with the following:

class CheckModulesTest : KoinTest {

@Test
fun verifyKoinApp() {

koinApplication {
modules(myModule)
checkModules(){
// value to add to Koin, used by definition
withInstance("_my_id_value")
}
}
}
}

This way, FactoryPresenter definition will be injected with "_my_id_value" define above.

You can also use mocked instances, to fill up your graph. You can notice that we need a MockProviderRule declaration to allow Koin mock any injected definition

val myModule1 = module {
factory { (a : ComponentA) -> ComponentB(a) }
}
// or
val myModule2 = module {
factory { ComponentB(get()) }
}
class CheckModulesTest : KoinTest {

@get:Rule
val mockProvider = MockProviderRule.create { clazz ->
// Setup your nock framework
Mockito.mock(clazz.java)
}

@Test
fun verifyKoinApp() {

koinApplication {
modules(myModule1)
checkModules(){
// add a mock of ComponentA to Koin
withInstance<ComponentA>()
}
}
}
}

Checking Modules for Android (3.1.3)

Here below is how you can test your graph for a typical Android app:

class CheckModulesTest {

@get:Rule
val rule: TestRule = InstantTaskExecutorRule()

@get:Rule
val mockProvider = MockProviderRule.create { clazz ->
Mockito.mock(clazz.java)
}

@Test
fun `test DI modules`(){
koinApplication {
modules(allModules)
checkModules(){
withInstance<Context>()
withInstance<Application>()
withInstance<SavedStateHandle>()
withInstance<WorkerParameters>()
}
}
}
}

also possible to use checkKoinModules:

class CheckModulesTest {

@get:Rule
val rule: TestRule = InstantTaskExecutorRule()

@get:Rule
val mockProvider = MockProviderRule.create { clazz ->
Mockito.mock(clazz.java)
}

@Test
fun `test DI modules`(){
checkKoinModules(allModules) {
withInstance<Context>()
withInstance<Application>()
withInstance<SavedStateHandle>()
withInstance<WorkerParameters>()
}
}
}

Providing Default Values (3.1.4)

If you need, you can set a default value for all type in the checked modules. For example, we can override all injected string values:

Let's use the withInstance() function in checkModules block, to define a default value for all definitions:

@Test
fun `test DI modules`(){
koinApplication {
modules(allModules)
checkModules(){
withInstance("_ID_")
}
}
}

All injected definition that are using a injected String parameter, will receive "_ID_":

module {
single { (i: String) -> Simple.ComponentC(i) }
factory { (id: String) -> FactoryPresenter(id) }
}

Providing ParametersOf values (3.1.4)

You can define a default value to be injected for one specific definition, with withParameter or withParameters functions:

@Test
fun `test DI modules`(){
koinApplication {
modules(allModules)
checkModules(){
withParameter<FactoryPresenter> { "_FactoryId_" }
withParameters<FactoryPresenter> { parametersOf("_FactoryId_",...) }
}
}
}

You can link scopes by using withScopeLink function incheckModules block to inject instances from another scope's definitions:

val myModule = module {
scope(named("scope1")) {
scoped { ComponentA() }
}
scope(named("scope2")) {
scoped { ComponentB(get()) }
}
}
@Test
fun `test DI modules`(){
koinApplication {
modules(myModule)
checkModules(){
withScopeLink(named("scope2"), named("scope1"))
}
}
}