Skip to content

Commit

Permalink
Merge pull request #1977 from InsertKoinIO/verify_parameter_injection
Browse files Browse the repository at this point in the history
Verify API - allow declare parameter injection type to be verified
  • Loading branch information
arnaudgiuliani authored Sep 13, 2024
2 parents e592cf2 + 7eb8ed9 commit 98baec9
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 96 deletions.
53 changes: 4 additions & 49 deletions docs/reference/koin-test/checkmodules.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,12 @@
---
title: Verifying your Koin configuration
title: CheckModules - Check Koin configuration (Deprecated)
---

:::note
Koin allows you to verify your configuration modules, avoiding discovering dependency injection issues at runtime.
:::warning
This API is now deprecated - since Koin 4.0
:::


### 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.

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


```kotlin
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 extra Types parameter 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/stub to run on your configuration.
Koin allows you to verify your configuration modules, avoiding discovering dependency injection issues at runtime.


### Koin Dynamic Check - CheckModules()
Expand Down
93 changes: 93 additions & 0 deletions docs/reference/koin-test/verify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
title: Verifying your Koin configuration
---

Koin allows you to verify your configuration modules, avoiding discovering 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.

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

```kotlin
class NiaAppModuleCheck {

@Test
fun checkKoinModule() {

// Verify Koin configuration
niaAppModule.verify()
}
}
```


Launch the JUnit test and you're done! ✅


As you may see, we use the extra Types parameter 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/stub to run on your configuration.

## Verifying with Injected Parameters - JVM Only [4.0]

When you have a configuration that implies injected obects with `parametersOf`, the verification will fail because there is no definition of the parameter's type in your configuration.
However you can define a parameter type, to be injected with given definition `definition<Type>(Class1::class, Class2::class ...)`.

Here is how it goes:

```kotlin
class ModuleCheck {

// given a definition with an injected definition
val module = module {
single { (a: Simple.ComponentA) -> Simple.ComponentB(a) }
}

@Test
fun checkKoinModule() {

// Verify and declare Injected Parameters
module.verify(
injections = injectedParameters(
definition<Simple.ComponentB>(Simple.ComponentA::class)
)
)
}
}
```

## Type White-Listing

We can add types as "white-listed". This means that this type is considered as present in the system for any definition. Here is how it goes:

```kotlin
class NiaAppModuleCheck {

@Test
fun checkKoinModule() {

// Verify Koin configuration
niaAppModule.verify(
// List types used in definitions but not declared directly (like parameters injection)
extraTypes = listOf(MyType::class ...)
)
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.work.WorkerParameters
import org.koin.android.test.verify.AndroidVerify.androidTypes
import org.koin.core.module.Module
import org.koin.test.verify.MissingKoinDefinitionException
import org.koin.test.verify.ParameterTypeInjection
import kotlin.reflect.KClass

/**
Expand All @@ -20,8 +21,8 @@ import kotlin.reflect.KClass
* @param extraTypes - allow to declare extra type, to be bound above the existing definitions
* @throws MissingKoinDefinitionException
*/
fun Module.verify(extraTypes: List<KClass<*>> = listOf()) {
org.koin.test.verify.Verify.verify(this,extraTypes + androidTypes)
fun Module.verify(extraTypes: List<KClass<*>> = listOf(), injections: List<ParameterTypeInjection>? = null) {
org.koin.test.verify.Verify.verify(this,extraTypes + androidTypes, injections)
}

/**
Expand All @@ -35,8 +36,8 @@ fun Module.verify(extraTypes: List<KClass<*>> = listOf()) {
* @param extraTypes - allow to declare extra type, to be bound above the existing definitions
* @throws MissingKoinDefinitionException
*/
fun Module.androidVerify(extraTypes: List<KClass<*>> = listOf()) {
org.koin.test.verify.Verify.verify(this,extraTypes + androidTypes)
fun Module.androidVerify(extraTypes: List<KClass<*>> = listOf(), injections: List<ParameterTypeInjection>? = null) {
org.koin.test.verify.Verify.verify(this,extraTypes + androidTypes, injections)
}

object AndroidVerify {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import org.koin.core.logger.EmptyLogger
import org.koin.dsl.koinApplication
import org.koin.dsl.module
import org.koin.test.KoinTest
import org.koin.test.verify.definition
import org.koin.test.verify.injectedParameters
import org.mockito.Mockito.mock

/**
Expand All @@ -29,17 +31,31 @@ class AndroidModuleTest : KoinTest {
single { AndroidComponentB(get()) }
single { AndroidComponentC(androidApplication()) }
single { OtherService(getProperty(URL)) }
single { p -> MyOtherService(p.get(),get()) }
}

class AndroidComponentA(val androidContext: Context)
class AndroidComponentB(val androidComponent: AndroidComponentA)
class AndroidComponentC(val application: Application)
class OtherService(val url: String)
class Id
class MyOtherService(val param : Id, val o: OtherService)

@Test
fun `should verify android module`() {
sampleModule.verify()
fun `should verify module`() {
sampleModule.verify(
injections = injectedParameters(
definition<MyOtherService>(Id::class)
)
)
}

sampleModule.androidVerify()
@Test
fun `should verify android module`() {
sampleModule.androidVerify(
injections = injectedParameters(
definition<MyOtherService>(Id::class)
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.koin.test

import org.koin.core.qualifier.Qualifier
import org.koin.mp.KoinPlatformTools
import org.koin.mp.generateId

@Suppress("unused")
class Simple {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.koin.test.verify

import org.koin.core.annotation.KoinExperimentalAPI
import kotlin.reflect.KClass

/**
* ParameterTypeInjection is a proposal to allow describe types that are dynamic, and needs injection parameters (use of parametersOf)
*
* @author Arnaud Giuliani
*/

/**
* Define Parameter Injection Types in order to help verify definition
*/
@KoinExperimentalAPI
data class ParameterTypeInjection(val targetType : KClass<*>, val injectedTypes : List<KClass<*>>)

/**
* Define injection for a definition Type
* @param T - definition type
* @param injectedParameterTypes - Types that need to be injected later with parametersOf
*/
@KoinExperimentalAPI
inline fun <reified T> definition(vararg injectedParameterTypes : KClass<*>): ParameterTypeInjection{
return ParameterTypeInjection(T::class, injectedParameterTypes.toList())
}

/**
* Define injection for a definition Type
* @param T - definition type
* @param injectedParameterTypes - Types that need to be injected later with parametersOf
*/
@KoinExperimentalAPI
inline fun <reified T> definition(injectedParameterTypes : List<KClass<*>>): ParameterTypeInjection{
return ParameterTypeInjection(T::class, injectedParameterTypes)
}

/**
* Declare list of ParameterTypeInjection - in order to help define parmater injection types to allow in verify
* @param injectionType - list of ParameterTypeInjection
*/
@KoinExperimentalAPI
fun injectedParameters(vararg injectionType : ParameterTypeInjection) : List<ParameterTypeInjection>{
return injectionType.toList()
}
Loading

0 comments on commit 98baec9

Please sign in to comment.