如何通过模拟服务替换Ktor路线测试中的Koin服务注入

时间:2019-05-08 15:02:20

标签: kotlin mocking mockito ktor koin

我想对使用Koin注入服务的Ktor路由进行测试。我正在努力模拟测试中Ktor路线使用的服务。

这是Ktor Application.kt 文件

@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
    install(Locations) {
    }
    // Suppressed code
    installKoin(listOf(dependencyInjectionModule))

    routing {

        val eventService by inject<EventService>()

        get("/issues/{issueNumber}/events") {

            val issue = call.parameters["issueNumber"]
            val issueNumber = parseInt(issue)
            call.respond(eventService.getEventsByIssueNumber(issueNumber))
        }      
    }
    // Suppressed code
}

Koin在上面工作,问题是当我需要模拟而不是使用Koin时。

这是 AplicationTest.kt 文件

class ApplicationTest {

    private val eventDtoList = listOfNotNull(
        EventDto(1, "opened", "2019-03-24T21:40:18Z", 1 ),
        EventDto(2, "closed", "2019-03-28T21:40:18Z", 1 )
    )

    @Test
    fun testGettingEventsByExistingIssueNumber() {

        withTestApplication({ module(testing = true) }) {

            stopKoin()

            handleRequest(HttpMethod.Get, "/issues/1/events").apply {
                val eventService:EventService = mock {
                    onBlocking { getEventsByIssueNumber(any()) } doReturn eventDtoList
                }

                val resultList = Gson().fromJson(response.content, Array<EventDto>::class.java).asList()

                assertEquals(response.status(), HttpStatusCode.OK)
                assertThat(resultList).hasSize(2)

            }
        }
    }
}

我想模拟服务返回的eventList,但是我到达这一行时:

withTestApplication({ module(testing = true) })

我收到此异常:

2019-05-08 11:51:40:881 (KOIN)::[e] Error while resolving instance for class 'com.service.EventService' - error: org.koin.error.NoBeanDefFoundException: No compatible definition found for type 'EventService'. Check your module definition 

org.koin.error.NoBeanDefFoundException: No compatible definition found for type 'EventService'. Check your module definition

    at org.koin.core.bean.BeanRegistry.retrieveDefinition(BeanRegistry.kt:113)
    at org.koin.core.instance.InstanceRegistry$proceedResolution$$inlined$synchronized$lambda$1.invoke(InstanceRegistry.kt:87)
    at org.koin.core.instance.InstanceRegistry$proceedResolution$$inlined$synchronized$lambda$1.invoke(InstanceRegistry.kt:36)
    at org.koin.core.time.DurationKt.measureDuration(Duration.kt:8)
    at org.koin.core.instance.InstanceRegistry.proceedResolution(InstanceRegistry.kt:84)
    at org.koin.core.instance.InstanceRegistry.resolve(InstanceRegistry.kt:63)
    at org.koin.core.instance.InstanceRegistry.resolve$default(InstanceRegistry.kt:48)
    at com.jaya.octovevent.ApplicationKt$module$4$$special$$inlined$inject$1.invoke(KtorRoutingExt.kt:112)
    at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
    at com.jaya.octovevent.ApplicationKt$module$4$2.invokeSuspend(Application.kt:76)
    at com.ApplicationKt$module$4$2.invoke(Application.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.features.StatusPages$interceptCall$2.invokeSuspend(StatusPages.kt:94)
    at io.ktor.features.StatusPages$interceptCall$2.invoke(StatusPages.kt)
    at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
    at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:180)
    at io.ktor.features.StatusPages.interceptCall(StatusPages.kt:93)
    at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:133)
    at io.ktor.features.StatusPages$Feature$install$2.invoke(StatusPages.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
    at io.ktor.routing.Routing.executeResult(Routing.kt:148)
    at io.ktor.routing.Routing.interceptor(Routing.kt:29)
    at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:93)
    at io.ktor.routing.Routing$Feature$install$1.invoke(Routing.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:63)
    at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
    at io.ktor.server.testing.TestApplicationEngine$2.invokeSuspend(TestApplicationEngine.kt:265)
    at io.ktor.server.testing.TestApplicationEngine$2.invoke(TestApplicationEngine.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
    at io.ktor.server.testing.TestApplicationEngine$handleRequest$pipelineJob$1.invokeSuspend(TestApplicationEngine.kt:263)
    at |b|b|b(Coroutine boundary.|b(|b)
    at io.ktor.features.StatusPages.interceptCall(StatusPages.kt:93)
    at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:133)
    at io.ktor.routing.Routing.executeResult(Routing.kt:148)
    at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:93)
    at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:63)
    at io.ktor.server.testing.TestApplicationEngine$2.invokeSuspend(TestApplicationEngine.kt:265)
    at io.ktor.server.testing.TestApplicationEngine$handleRequest$pipelineJob$1.invokeSuspend(TestApplicationEngine.kt:263)
    at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:98)
    at io.ktor.server.testing.TestApplicationEngine$handleRequest$1.invokeSuspend(TestApplicationEngine.kt:129)
Caused by: org.koin.error.NoBeanDefFoundException: No compatible definition found for type 'EventService'. Check your module definition
    at org.koin.core.bean.BeanRegistry.retrieveDefinition(BeanRegistry.kt:113)
    at org.koin.core.instance.InstanceRegistry$proceedResolution$$inlined$synchronized$lambda$1.invoke(InstanceRegistry.kt:87)
    at org.koin.core.instance.InstanceRegistry$proceedResolution$$inlined$synchronized$lambda$1.invoke(InstanceRegistry.kt:36)
    at org.koin.core.time.DurationKt.measureDuration(Duration.kt:8)
    at org.koin.core.instance.InstanceRegistry.proceedResolution(InstanceRegistry.kt:84)
    at org.koin.core.instance.InstanceRegistry.resolve(InstanceRegistry.kt:63)
    at org.koin.core.instance.InstanceRegistry.resolve$default(InstanceRegistry.kt:48)
    at com.ApplicationKt$module$4$$special$$inlined$inject$1.invoke(KtorRoutingExt.kt:112)
    at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
    at com.ApplicationKt$module$4$2.invokeSuspend(Application.kt:76)
    at com.ApplicationKt$module$4$2.invoke(Application.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.features.StatusPages$interceptCall$2.invokeSuspend(StatusPages.kt:94)
    at io.ktor.features.StatusPages$interceptCall$2.invoke(StatusPages.kt)
    at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
    at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:180)
    at io.ktor.features.StatusPages.interceptCall(StatusPages.kt:93)
    at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:133)
    at io.ktor.features.StatusPages$Feature$install$2.invoke(StatusPages.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
    at io.ktor.routing.Routing.executeResult(Routing.kt:148)
    at io.ktor.routing.Routing.interceptor(Routing.kt:29)
    at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:93)
    at io.ktor.routing.Routing$Feature$install$1.invoke(Routing.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:63)
    at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
    at io.ktor.server.testing.TestApplicationEngine$2.invokeSuspend(TestApplicationEngine.kt:265)
    at io.ktor.server.testing.TestApplicationEngine$2.invoke(TestApplicationEngine.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
    at io.ktor.server.testing.TestApplicationEngine$handleRequest$pipelineJob$1.invokeSuspend(TestApplicationEngine.kt:263)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)

我知道Koin找不到EventService实现,因为我已经停止了它(Koin)。那么我该如何通过模拟服务替换Koin注入?

预先感谢

2 个答案:

答案 0 :(得分:1)

如果您试图设置用于测试/模拟的代码,而不是尝试研究Koin的功能,那么我这样做的方法就是分解内容,例如:

fun Application.module(testing: Boolean = false) {

    val todoService: TodoService by inject()
    moduleWithDependencies(eventService)
}

fun Application.moduleWithDependencies(eventService: EventService) {
    install(Routing) {
        eventApi(eventService)
    }
}

eventApi有路线,然后我可以独立测试

这里有一个示例(不是很完整)https://github.com/kevinrjones/koodoo

答案 1 :(得分:0)

问题在于您是在Application.module()内部启动Koin。

最好在呼叫Application.module()之前启动Koin。因此,您可以在生产和测试环境中初始化不同的依赖关系。

这是开始生产环境的方法:

fun main(args: Array<String>) {
    val appEnvironment = commandLineEnvironment(args)
    startKoin { modules(initAppModule(appEnvironment.config)) }
    embeddedServer(Netty, appEnvironment).start(true)
}

这是测试环境:

fun testApp(test: TestApplicationEngine.() -> Unit) {
    withApplication(createTestEnvironment {
        config = HoconApplicationConfig(ConfigFactory.load("application.conf"))
        startKoin { modules(initTestAppModule(config)) }
    }, {
    }, test)

    val koin = KoinContextHandler.get()
    stopKoin() // Stop koin after test
}

在此处查看示例:https://github.com/comm1x/ktor-boot/blob/master/src/main.kt

在那里:https://github.com/comm1x/ktor-boot/blob/master/test/common/common.kt