Skip to content

Commit

Permalink
Add support for scope linking in checkModules
Browse files Browse the repository at this point in the history
on-behalf-of: @e-solutions-GmbH <info@esolutions.de>
  • Loading branch information
Pfoerd committed Mar 2, 2022
1 parent 4177583 commit 37a6e34
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 10 deletions.
Expand Up @@ -27,6 +27,12 @@ class CheckModulesTest {
single { p -> Simple.ComponentB(p.get()) }
single(named("param")) { p -> Simple.MyString(p.get()) }
single { Simple.MyString(getProperty("aValue")) }
scope(named("scope1")) {
scoped { Simple.ComponentD() }
}
scope(named("scope2")) {
scoped { Simple.ComponentE(get()) }
}
}

koinApplication {
Expand All @@ -36,6 +42,7 @@ class CheckModulesTest {
// withInstance<String>("a_parameter")
withParameter<String> { "a_parameter" }
withProperty("aValue", "string_value")
withScopeLink(named("scope2"), named("scope1"))
}
}
}
Expand All @@ -46,12 +53,19 @@ class CheckModulesTest {
single { p -> Simple.ComponentB(p.get()) }
single(named("param")) { p -> Simple.MyString(p.get()) }
single { Simple.MyString(getProperty("aValue")) }
scope(named("scope1")) {
scoped { Simple.ComponentD() }
}
scope(named("scope2")) {
scoped { Simple.ComponentE(get()) }
}
}

checkKoinModules(listOf(modules)) {
withInstance<Simple.ComponentA>()
withParameter<String> { "a_parameter" }
withProperty("aValue", "string_value")
withScopeLink(named("scope2"), named("scope1"))
}
}

Expand Down Expand Up @@ -276,7 +290,7 @@ class CheckModulesTest {
}
)
}.checkModules()
fail("should not pass with borken definitions")
fail("should not pass with broken definitions")
} catch (e: Exception) {
e.printStackTrace()
}
Expand Down Expand Up @@ -504,4 +518,24 @@ class CheckModulesTest {
}
}
}

@Test
fun `check a module with linked scopes`() {
koinApplication {
printLogger(Level.DEBUG)
properties(hashMapOf("aValue" to "value"))
modules(
module {
scope<Simple.ComponentA> {
scoped { Simple.ComponentD() }
}
scope<Simple.ComponentB> {
scoped { Simple.ComponentE(get()) }
}
}
)
}.checkModules {
withScopeLink<Simple.ComponentB, Simple.ComponentA>()
}
}
}
Expand Up @@ -8,11 +8,14 @@ class Simple {
class ComponentA
class ComponentB(val a: ComponentA)
class ComponentC(val b: ComponentB)
class ComponentD()
class ComponentE(val d: ComponentD)
class MyString(val s: String)

class UUIDComponent {
fun getUUID() = UUID.randomUUID().toString()
}

}

object UpperCase : Qualifier {
Expand Down
Expand Up @@ -111,19 +111,23 @@ private fun Koin.declareParameterCreators(parametersDefinition: CheckParameters?

@OptIn(KoinInternalApi::class)
private fun Koin.checkAllDefinitions(allParameters: ParametersBinding) {
val scopes: List<Scope> = instantiateAllScopes(allParameters)
allParameters.scopeLinks.forEach { scopeLink ->
val linkTargets = scopes.filter { it.scopeQualifier == scopeLink.value }
scopes.filter { it.scopeQualifier == scopeLink.key }
.forEach { scope -> linkTargets.forEach { linkTarget -> scope.linkTo(linkTarget) } }
}
instanceRegistry.instances.values.toSet().forEach { factory ->
check(factory,allParameters)
checkDefinition(allParameters, factory.beanDefinition, scopes.first { it.scopeQualifier == factory.beanDefinition.scopeQualifier })
}
}

private fun Koin.check(
factory: InstanceFactory<*>,
allParameters: ParametersBinding
) {
val qualifier = factory.beanDefinition.scopeQualifier
val sourceScopeValue = mockSourceValue(qualifier)
val scope = getOrCreateScope(qualifier.value, qualifier, sourceScopeValue)
checkDefinition(allParameters,factory.beanDefinition,scope)
@OptIn(KoinInternalApi::class)
private fun Koin.instantiateAllScopes(allParameters: ParametersBinding): List<Scope> {
return scopeRegistry.scopeDefinitions.map { qualifier ->
val sourceScopeValue = mockSourceValue(qualifier)
getOrCreateScope(qualifier.value, qualifier, sourceScopeValue)
}
}

private fun mockSourceValue(qualifier: Qualifier): Any? {
Expand Down
Expand Up @@ -19,6 +19,7 @@ import org.koin.core.Koin
import org.koin.core.parameter.ParametersHolder
import org.koin.core.parameter.parametersOf
import org.koin.core.qualifier.Qualifier
import org.koin.core.qualifier.qualifier
import org.koin.mp.KoinPlatformTools
import org.koin.test.mock.MockProvider
import kotlin.reflect.KClass
Expand All @@ -29,6 +30,7 @@ class ParametersBinding(val koin: Koin) {

val parametersCreators = mutableMapOf<CheckedComponent, ParametersCreator>()
val defaultValues = mutableMapOf<String, Any>()
val scopeLinks = mutableMapOf<Qualifier, Qualifier>()

@Deprecated("use withParameter() instead", ReplaceWith("withParameters(qualifier,creator)"))
inline fun <reified T> create(qualifier: Qualifier? = null, noinline creator: ParametersCreator) =
Expand Down Expand Up @@ -62,6 +64,9 @@ class ParametersBinding(val koin: Koin) {
defaultValues.put(KoinPlatformTools.getClassName(T::class), MockProvider.makeMock<T>())

fun withProperty(key: String, value: Any) = koin.setProperty(key, value)
inline fun <reified T : Any, reified U : Any> withScopeLink() = withScopeLink(qualifier<T>(), qualifier<U>())
inline fun withScopeLink(scopeQualifier: Qualifier, targetScopeQualifier: Qualifier) =
scopeLinks.put(scopeQualifier, targetScopeQualifier)
}

typealias ParametersCreator = (Qualifier?) -> ParametersHolder
Expand Down
27 changes: 27 additions & 0 deletions docs/reference/koin-test/checkmodules.md
Expand Up @@ -226,3 +226,30 @@ fun `test DI modules`(){
}
}
```

#### Providing Scope Links

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

```kotlin
val myModule = module {
scope(named("scope1")) {
scoped { ComponentA() }
}
scope(named("scope2")) {
scoped { ComponentB(get()) }
}
}
```

```kotlin
@Test
fun `test DI modules`(){
koinApplication {
modules(myModule)
checkModules(){
withScopeLink(named("scope2"), named("scope1"))
}
}
}
```

0 comments on commit 37a6e34

Please sign in to comment.