SpringBoot 3.2 Mockk测试RestClient:不同请求体被识别为同一请求
如何验证SpringBoot 3.2 RestClient调用的请求体正确性?
我在使用SpringBoot 3.2新引入的RestClient时,业务逻辑会两次调用同一API但传入不同请求体。测试时用Mockk模拟RestClient请求,原本期望验证两个不同请求体的调用各执行一次(之前用RestTemplate时逻辑正常),但测试失败,两个请求被识别为同一请求。
测试代码如下:
@Test fun `test API call 2 times`() { every { restClient .post() .uri(URL) .headers(any()) .body(APIRequest(foo)) .retrieve() .toEntity(String::class.java) } returns ResponseEntity(HttpStatus.OK) } every { restClient .post() .uri(URL) .headers(any()) .body(APIRequest(bar)) .retrieve() .toEntity(String::class.java) } returns ResponseEntity(HttpStatus.OK) } service.process() verify(exactly = 1) { restClient .post() .uri(URL) .headers(any()) .body(APIRequest(foo)) .retrieve() .toEntity(String::class.java) } verify(exactly = 1) { restClient .post() .uri(URL) .headers(any()) .body(APIRequest(bar)) .retrieve() .toEntity(String::class.java) } }
测试失败报错:
java.lang.AssertionError: Verification failed: call 1 of 6: RestClient(restClient#2).post()). 2 matching calls found, but needs at least 1 and at most 1 calls Calls: 1) RestClient(restClient#2).post() 2) RestClient(restClient#2).post()
试过用@RestClientTest,但它只能模拟端点响应,没法验证请求体。请问怎么验证RestClient实际调用时用了正确的请求体?
问题原因
RestClient的调用链是流式API,Mockk在匹配链式调用时,默认会把post()后的所有方法调用视为同一链的一部分,但如果直接按完整链匹配,容易因为对象相等性(比如APIRequest的equals方法未正确实现)或者链式调用的匹配逻辑导致识别错误。
解决方法
1. 确保APIRequest实现正确的equals和hashCode
如果APIRequest是自定义普通类,必须手动实现equals和hashCode;如果是Kotlin数据类,默认已自动生成该逻辑,可跳过此步。只有对象相等性判断正确,Mockk才能准确匹配不同的请求体。
2. 使用参数捕获器(CapturingSlot)验证请求体
通过Mockk的CapturingSlot捕获所有调用的请求体,再断言捕获内容符合预期:
@Test fun `test API call 2 times`() { // 定义列表存储所有请求体捕获槽 val requestSlots = mutableListOf<CapturingSlot<APIRequest>>() // 匹配所有post请求,捕获body参数 every { restClient .post() .uri(URL) .headers(any()) .body(capture(requestSlots)) .retrieve() .toEntity(String::class.java) } returns ResponseEntity(HttpStatus.OK) service.process() // 验证总调用次数 verify(exactly = 2) { restClient .post() .uri(URL) .headers(any()) .body(any<APIRequest>()) .retrieve() .toEntity(String::class.java) } // 验证捕获到的请求体内容 val capturedRequests = requestSlots.map { it.captured } assert(capturedRequests.containsAll(listOf(APIRequest(foo), APIRequest(bar)))) assert(capturedRequests.size == 2) }
3. 用match函数精确匹配请求体
如果不想依赖对象的equals方法,可以用Mockk的match函数自定义匹配逻辑:
@Test fun `test API call 2 times`() { every { restClient .post() .uri(URL) .headers(any()) .body(match { it.foo == foo }) .retrieve() .toEntity(String::class.java) } returns ResponseEntity(HttpStatus.OK) every { restClient .post() .uri(URL) .headers(any()) .body(match { it.foo == bar }) .retrieve() .toEntity(String::class.java) } returns ResponseEntity(HttpStatus.OK) service.process() verify(exactly = 1) { restClient .post() .uri(URL) .headers(any()) .body(match { it.foo == foo }) .retrieve() .toEntity(String::class.java) } verify(exactly = 1) { restClient .post() .uri(URL) .headers(any()) .body(match { it.foo == bar }) .retrieve() .toEntity(String::class.java) } }
4. 拆分链式调用的Mock对象
把RestClient的链式调用拆分成多个中间对象的Mock,精准匹配每一步调用:
@Test fun `test API call 2 times`() { // Mock链式调用的各个中间对象 val requestBodyUriSpec = mockk<RestClient.RequestBodyUriSpec>() val requestBodySpec = mockk<RestClient.RequestBodySpec>() val responseSpec = mockk<RestClient.ResponseSpec>() every { restClient.post() } returns requestBodyUriSpec every { requestBodyUriSpec.uri(URL) } returns requestBodySpec every { requestBodySpec.headers(any()) } returns requestBodySpec every { requestBodySpec.retrieve() } returns responseSpec every { responseSpec.toEntity(String::class.java) } returns ResponseEntity(HttpStatus.OK) // 分别匹配两个请求体 every { requestBodySpec.body(APIRequest(foo)) } returns requestBodySpec every { requestBodySpec.body(APIRequest(bar)) } returns requestBodySpec service.process() // 验证两个请求体各被调用一次 verify(exactly = 1) { requestBodySpec.body(APIRequest(foo)) } verify(exactly = 1) { requestBodySpec.body(APIRequest(bar)) } }
内容的提问来源于stack exchange,提问作者truongnm
相关产品推荐
相关产品推荐

