InsertKoinIO / koin

Koin - a pragmatic lightweight dependency injection framework for Kotlin & Kotlin Multiplatform
https://insert-koin.io
Apache License 2.0
9.1k stars 720 forks source link

Unable to use `declareMock` to override a dependency in Kotest #2051

Open mityakoval opened 2 weeks ago

mityakoval commented 2 weeks ago

Describe the bug In a Ktor server the single injected with a declareMock method in testApplication in a KoinTest/Kotest test doesn't override the service in main package.

class ApplicationTest : FunSpec(), KoinTest {

    override fun extensions(): List<Extension> = listOf(
        KoinExtension(
            module = module {},
            mockProvider = {
                mockkClass(it)
            },
            mode = KoinLifecycleMode.Root
        )
    )

    init {
        val appConfig = listOf(
            ApplicationConfig("application.test.conf")
        ).reduce(ApplicationConfig::mergeWith)

        test("Test mock injection") {
            testApplication {
                environment {
                    config = appConfig
                }

                application {
                    val serviceBMock = declareMock<ServiceB>()

                    every { serviceBMock.identify() }.returns("Mock")
                }

                val cli = createClient {
                    install(ContentNegotiation) {
                        json()
                    }
                }

                val resp = cli.get("/test")
                val body = resp.body<Response>()
                body.a shouldBe "ServiceA"
                body.b shouldBe "Mock"

            }
        }
    }
}

Noticed that this issue is arising if there's a somewhat complicated routing setup (see reproduction repo for more details):

fun Application.configureRouting() {
    testRouting()
}

...

fun Application.testRouting() {
    routing {
        val serviceA by inject<ServiceA>()
        val serviceB by inject<ServiceB>()
        test(serviceA, serviceB)
    }
}

@Serializable
data class Response(val a: String, val b: String)

fun Route.test(serviceA: ServiceA, serviceB: ServiceB) {
    get("/test") {
        call.respond(status = HttpStatusCode.OK, Response(serviceA.identify(), serviceB.identify()))
    }
}

To Reproduce Here's a repo with a sample project that reproduces this issue: https://github.com/mityakoval/KoinKotestSample

Just run ApplicationTest suite there

Expected behavior I expected the declareMock method to inject the object created by Mockk in to Koin to be used in test instead of the service declared in the main application.

Koin module and version: koin-ktor:4.0.0 koin-test:4.0.0 Ktor:2.3.12 kotest-runner-junit5:5.9.1 kotest-extensions-koin:1.3.0

Snippet or Sample project to help reproduce Sample project to reproduce: https://github.com/mityakoval/KoinKotestSample