最简Ktor-server-test-host+Kotest测试遭遇连接拒绝问题
解决Ktor Server Test Host连接拒绝/Job已完成错误
问题背景
使用ktor-server-test-host编写极简测试时遇到两类错误:默认客户端请求时抛出JobCancellationException(提示Parent job is Completed),创建自定义客户端时抛出IllegalStateException(提示EmbeddedServer was stopped)。
依赖版本
ktor:3.1.2 (包含ktor-server-test-host) kotest:5.9.1 kotlin:2.1.20 (JVM)
原测试代码
package bps.budget.server import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.ktor.client.HttpClient import io.ktor.client.request.get import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.http.contentType import io.ktor.http.withCharset import io.ktor.server.response.respondText import io.ktor.server.routing.get import io.ktor.server.routing.routing import io.ktor.server.testing.testApplication class SimpleKtorTest : FreeSpec() { init { testApplication { application { routing { get("/") { call.respondText("root") } } } "/" { val response: HttpResponse = client.get("/") response.status shouldBe HttpStatusCode.OK response.contentType() shouldBe ContentType.Text.Plain.withCharset(Charsets.UTF_8) response.bodyAsText() shouldBe "root" } } } }
错误信息
默认客户端请求时的错误
Parent job is Completed kotlinx.coroutines.JobCancellationException: Parent job is Completed; job=SupervisorJobImpl{Completed}@301c7f3e at bps.budget.server.SimpleKtorTest$1$2.invokeSuspend(SimpleKtorTest.kt:51) at bps.budget.server.SimpleKtorTest$1$2.invoke(SimpleKtorTest.kt) at bps.budget.server.SimpleKtorTest$1$2.invoke(SimpleKtorTest.kt) at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:42) at bps.budget.server.SimpleKtorTest$1$2.invokeSuspend(SimpleKtorTest.kt:51) Caused by: kotlinx.coroutines.JobCancellationException: Parent job is Completed; job=SupervisorJobImpl{Completed}@301c7f3e
创建自定义客户端时的错误
EmbeddedServer was stopped java.lang.IllegalStateException: EmbeddedServer was stopped at io.ktor.server.engine.EmbeddedServer.currentApplication(EmbeddedServerJvm.kt:111) at io.ktor.server.engine.EmbeddedServer.access$currentApplication(EmbeddedServerJvm.kt:36) at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:42) at bps.budget.server.SimpleKtorTest$1$2.invokeSuspend(SimpleKtorTest.kt:46) at io.kotest.core.spec.style.scopes.FreeSpecRootScope$invoke$1.invokeSuspend(FreeSpecRootScope.kt:26) Caused by: java.lang.IllegalStateException: EmbeddedServer was stopped at io.ktor.server.engine.EmbeddedServer.currentApplication(EmbeddedServerJvm.kt:111)
解决方法
核心问题是测试结构不符合Kotest与Ktor的协作规则:testApplication块应嵌套在Kotest的测试节点(如FreeSpec的字符串块)内部,而非反过来将测试节点放在testApplication中。
修正后的代码
package bps.budget.server import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.ktor.client.request.get import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.http.contentType import io.ktor.http.withCharset import io.ktor.server.response.respondText import io.ktor.server.routing.get import io.ktor.server.routing.routing import io.ktor.server.testing.testApplication class SimpleKtorTest : FreeSpec() { init { "测试根路径响应" { testApplication { application { routing { get("/") { call.respondText("root") } } } val response: HttpResponse = client.get("/") response.status shouldBe HttpStatusCode.OK response.contentType() shouldBe ContentType.Text.Plain.withCharset(Charsets.UTF_8) response.bodyAsText() shouldBe "root" } } } }
原因说明
- Kotest的
FreeSpec通过内部测试节点管理测试生命周期,每个节点对应独立测试任务。 - 原代码将
testApplication放在init最外层,导致Kotest测试节点在testApplication作用域结束后执行,此时测试服务器已关闭,触发连接拒绝/服务器已停止错误。 - 修正后
testApplication嵌套在测试节点内,确保服务器在测试执行期间保持运行,请求可正常发送。
自定义客户端使用示例
若需自定义客户端,直接在testApplication内部调用createClient即可:
"测试自定义客户端" { testApplication { application { routing { get("/") { call.respondText("root") } } } val client = createClient { // 自定义配置,如添加插件、设置超时等 } val response = client.get("/") response.bodyAsText() shouldBe "root" } }
内容的提问来源于stack exchange,提问作者BPS
相关产品推荐
相关产品推荐

