Skip to content

Commit

Permalink
Merge pull request #1939 from pedrofsn/breaking_changes-issue_1938
Browse files Browse the repository at this point in the history
Restoring CheckModules as deprecated code
  • Loading branch information
arnaudgiuliani authored Aug 27, 2024
2 parents 5ca831e + 571d1d0 commit b2452ad
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* Copyright 2017-Present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("UNUSED_PARAMETER")
@file:OptIn(KoinInternalApi::class)

package org.koin.test.check

import org.koin.core.Koin
import org.koin.core.KoinApplication
import org.koin.core.annotation.KoinInternalApi
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.core.definition.BeanDefinition
import org.koin.core.logger.Level
import org.koin.core.module.Module
import org.koin.core.parameter.ParametersHolder
import org.koin.core.qualifier.Qualifier
import org.koin.core.qualifier.TypeQualifier
import org.koin.core.scope.Scope
import org.koin.dsl.KoinAppDeclaration
import org.koin.mp.KoinPlatformTools
import org.koin.test.mock.MockProvider
import org.koin.test.parameter.MockParameter

//TODO TO BE DEPRECATED in 3.6

/**
* Check all definition's dependencies - start all modules and check if definitions can run
*/
@Deprecated(
message = "Migrate to verify() API",
replaceWith = ReplaceWith("org.koin.test.verify.Verify")
)
fun KoinApplication.checkModules(parameters: CheckParameters? = null) = koin.checkModules(parameters)

/**
* Verify Modules by running each definition
*
* @param level - Log level
* @param parameters - parameter setup
* @param appDeclaration - koin Application
*/
@Deprecated(
message = "Migrate to verify() API",
replaceWith = ReplaceWith("org.koin.test.verify.Verify")
)
fun checkModules(level: Level = Level.INFO, parameters: CheckParameters? = null, appDeclaration: KoinAppDeclaration) {
startKoin(appDeclaration)
.logger(KoinPlatformTools.defaultLogger(level))
.checkModules(parameters)
}

/**
* Check given modules directly
*
* @param modules - list of modules
* @param appDeclaration - Koin app config if needed
* @param parameters - Check parameters DSL
*/
@Deprecated(
message = "Migrate to verify() API",
replaceWith = ReplaceWith("modules.verifyAll()", "org.koin.test.verify.verifyAll")
)
fun checkKoinModules(modules: List<Module>, appDeclaration: KoinAppDeclaration = {}, parameters: CheckParameters? = null) {
startKoin(appDeclaration)
.modules(modules)
.checkModules(parameters)
stopKoin()
}

/**
* @see checkModules
*
* Deprecated
*/
@Deprecated("use instead checkKoinModules(modules : List<Module>, appDeclaration: KoinAppDeclaration = {}, parameters: CheckParameters? = null)")
fun checkKoinModules(level: Level = Level.INFO, parameters: CheckParameters? = null, appDeclaration: KoinAppDeclaration) {
startKoin(appDeclaration)
.logger(KoinPlatformTools.defaultLogger(level))
.checkModules(parameters)
stopKoin()
}

/**
* @see checkKoinModules
*
* Deprecated
*/
@Deprecated("use instead checkKoinModules(modules : List<Module>, appDeclaration: KoinAppDeclaration = {}, parameters: CheckParameters? = null)")
fun checkKoinModules(vararg modules: Module, level: Level = Level.INFO, parameters: CheckParameters? = null) {
startKoin({})
.logger(KoinPlatformTools.defaultLogger(level))
.checkModules(parameters)
stopKoin()
}

/**
* Check all definition's dependencies - start all modules and check if definitions can run
*/
@Deprecated(
message = "Migrate to verify() API",
replaceWith = ReplaceWith("org.koin.test.verify.Verify")
)
fun Koin.checkModules(parametersDefinition: CheckParameters? = null) {
logger.info("[Check] checking modules ...")

checkAllDefinitions(
declareParameterCreators(parametersDefinition),
)

logger.info("[Check] All checked")
close()
}

private fun Koin.declareParameterCreators(parametersDefinition: CheckParameters?) =
ParametersBinding(this).also { binding -> parametersDefinition?.invoke(binding) }

@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 ->
checkDefinition(allParameters, factory.beanDefinition, scopes.first { it.scopeQualifier == factory.beanDefinition.scopeQualifier })
}
}

@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? {
return if (qualifier is TypeQualifier) {
MockProvider.makeMock(qualifier.type)
} else {
null
}
}

private fun Koin.checkDefinition(
allParameters: ParametersBinding,
definition: BeanDefinition<*>,
scope: Scope,
) {
val parameters: ParametersHolder = allParameters.parametersCreators[
CheckedComponent(
definition.qualifier,
definition.primaryType,
),
]?.invoke(
definition.qualifier,
) ?: MockParameter(scope, allParameters.defaultValues)

logger.info("[Check] definition: $definition")
scope.get<Any>(definition.primaryType, definition.qualifier) { parameters }

for (secondaryType in definition.secondaryTypes) {
val valueAsSecondary = scope.get<Any>(secondaryType, definition.qualifier) { parameters }
require(secondaryType.isInstance(valueAsSecondary)) {
"instance of ${valueAsSecondary::class} is not inheritable from $secondaryType"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2017-Present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.koin.test.check

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

//TODO TO BE DEPRECATED in 3.6

@Deprecated("Migrate to verify() API")
data class CheckedComponent(val qualifier: Qualifier? = null, val type: KClass<*>)

@Deprecated("Migrate to verify() API")
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) =
parametersCreators.put(CheckedComponent(qualifier, T::class), creator)

inline fun <reified T> withParameter(qualifier: Qualifier? = null, noinline creator: ParametersInstance) =
parametersCreators.put(CheckedComponent(qualifier, T::class)) { q -> parametersOf(creator(q)) }

inline fun <reified T> withParameters(qualifier: Qualifier? = null, noinline creator: ParametersCreator) =
parametersCreators.put(CheckedComponent(qualifier, T::class), creator)

@Deprecated("use withParameter() instead", ReplaceWith("withParameters(clazz,qualifier,creator)"))
fun create(clazz: KClass<*>, qualifier: Qualifier? = null, creator: ParametersCreator) =
parametersCreators.put(CheckedComponent(qualifier, clazz), creator)

fun withParameter(clazz: KClass<*>, qualifier: Qualifier? = null, creator: ParametersInstance) =
parametersCreators.put(CheckedComponent(qualifier, clazz)) { q -> parametersOf(creator(q)) }

fun withParameters(clazz: KClass<*>, qualifier: Qualifier? = null, creator: ParametersCreator) =
parametersCreators.put(CheckedComponent(qualifier, clazz), creator)

@Deprecated("use withInstance() instead", ReplaceWith("withInstance(t)"))
inline fun <reified T : Any> defaultValue(t: T) = defaultValues.put(KoinPlatformTools.getClassName(T::class), t)

@Deprecated("use withInstance() instead", ReplaceWith("withInstance()"))
inline fun <reified T : Any> defaultValue() =
defaultValues.put(KoinPlatformTools.getClassName(T::class), MockProvider.makeMock<T>())

inline fun <reified T : Any> withInstance(t: T) = defaultValues.put(KoinPlatformTools.getClassName(T::class), t)
inline fun <reified T : Any> withInstance() =
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
typealias ParametersInstance = (Qualifier?) -> Any
typealias CheckParameters = ParametersBinding.() -> Unit
34 changes: 34 additions & 0 deletions projects/core/koin-test/src/jvmTest/kotlin/CheckModulesTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import kotlin.test.Test
import kotlin.test.fail
import org.koin.core.error.InstanceCreationException
import org.koin.dsl.module
import org.koin.test.Simple
import org.koin.test.check.checkKoinModules
import org.koin.test.verify.MissingKoinDefinitionException

class CheckModulesTest {

@Test
fun verify_one_simple_module() {
val module = module {
single { Simple.ComponentA() }
single { Simple.ComponentB(get()) }
}

try {
checkKoinModules(listOf(module))
} catch (e: MissingKoinDefinitionException) {
fail("Should not fail to verify module - $e")
}
}

@Test(expected = InstanceCreationException::class)
fun verify_one_simple_broken_module() {
val module = module {
single { Simple.ComponentB(get()) }
}

checkKoinModules(listOf(module))
fail("Should fail to verify module")
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import org.koin.core.logger.Level
import kotlin.test.Test
import kotlin.test.fail
import org.koin.core.module.dsl.factoryOf
import org.koin.core.module.dsl.singleOf
import org.koin.core.qualifier.named
import org.koin.dsl.bind
import org.koin.dsl.koinApplication
import org.koin.dsl.module
import org.koin.test.Simple
import org.koin.test.check.checkModules
import org.koin.test.verify.CircularInjectionException
import org.koin.test.verify.MissingKoinDefinitionException
import org.koin.test.verify.Verify
import org.koin.test.verify.verify
import kotlin.test.Test
import kotlin.test.fail

class VerifyModulesTest {

Expand Down

0 comments on commit b2452ad

Please sign in to comment.