【问题标题】:Unit test freezes during the execution单元测试在执行期间冻结
【发布时间】:2026-02-15 12:45:02
【问题描述】:

所以,我的问题在这里 - 我正在尝试为我的应用程序进行单元测试。我有 2 个服务,我们称它们为 Foo 和 Bar,而 Foo 只不过是 Bar 的代理。

所以,Foo 服务的路由器是这样的:

fun fooRoute(...) = coRouter {
    . . .
    GET("/something", fooHandler::findSomething)
    . . .
}

向 Bar 服务发出请求的处理程序如下所示:

fun fooHandler(barClient: WebClient) {
    . . .
    suspend fun findSomething(request: ServerRequest): ServerResponse {
        val response = barClient.get()
            .uri(...)
            .accept(...)
            .awaitExchange()
        . . .
        return when (response.statusCode()) {
            HttpStatus.OK -> {
                ServerResponse
                    . . .
                    .bodyValueAndAwait(response.awaitBody<SomeType>())
            }
            else -> { 
                ServerResponse
                    . . .
                    .buildAndAwait()
            }
        }
    }
    . . .
}

当我这样写测试时:

. . .
private val barClient = mockk<WebClient>()

private val fooHandler = FooHandler(barClient)
private val fooRouter = FooRouter()
private val fooClient = WebTestClient.bindToRouterFunction(
    fooRouter.fooRoute(
        // mocks
        . . .
        fooHandler
    )
).build()

@Nested
inner class FindSomething {

    @Test
    fun `returns OK`() = runBlocking {

        val response = mockk<ClientResponse>()
        val spec = mockk<WebClient.RequestHeadersUriSpec<*>>()

        coEvery { response.awaitBody<SomeType>() } returns SomeType(...)
        coEvery { spec.awaitExchange() } returns response
        coEvery { barClient.get() } returns spec

        fooClient
            .get()
            .uri(...)
            .exchange()
            .expectStatus().is2xxSuccessful

        . . .
    }
}

它只是永远冻结......好吧,我认为这是因为它周围有一些协程魔法,但因为我还是这个的新手,所以我无法理解这里到底发生了什么。有什么帮助吗?

【问题讨论】:

    标签: unit-testing kotlin webclient kotlin-coroutines mockk


    【解决方案1】:

    Welp,我通过基于this 答案编写自己的交换函数解决了这个问题。这是我如何做到这一点的一个例子:

    . . .
    fun setUpBarClient(exchangeFunction: ExchangeFunction) {
        barClient = barClient.mutate()
            .exchangeFunction(exchangeFunction)
            .build()
    
        // recreate fooHandler & fooClient
    }
    
    @Nested
    inner class FindSomething {
    
        @Test
        fun `returns OK`() {
    
            // this one from org.springframework.web.reactive.function.client.ExchangeFunction
    
            val exchangeFunction = ExchangeFunction {
                Mono.just(
                    ClientResponse.create(HttpStatus.OK)
                        .header(...)
                        .body(...)
                        .build()
                )
            }
    
            setUpBarClient(exchangeFunction)
    
            fooClient
                .get()
                .uri(...)
                .exchange()
                .expectStatus().is2xxSuccessful
    
            . . .
        }
    }
    

    【讨论】:

    • 顺便说一句,如果您对第二个客户有 2 次或更多连续调用,您可能会遇到我现在遇到的问题。问题是您一次只能提供一个交换功能,并且如果您正在等待来自客户端的另一个参数以进一步进行(例如,如果您想首先获得 status = OK,然后在下一步获得 status = NO_CONTENT ) 你必须以某种方式处理它。