You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

最简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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 16:40:19