Mockk库中HttpClient调用验证失败问题排查
Mock HttpClient调用未被检测到的原因分析
错误信息
java.lang.AssertionError: Verification failed: call 2 of 2: BlockingHttpClient(child of #1#3).retrieve(eq(POST /employee/create))) was not called
业务代码
@KafkaListener(offsetReset = OffsetReset.EARLIEST, groupId = "test") class EmployeeListener(@Client("\${demo.restapi}") val client: HttpClient) { var log = KotlinLogging.logger {} val gson = GsonBuilder().setPrettyPrinting().create(); @Topic("employee") fun recieveEmployee(str: String) { log.info { "Received employee creation request for $str" } try { val employeeDto = gson.fromJson(str, EmployeeDto::class.java) val request: HttpRequest<Any> = HttpRequest.POST("/employee/create", employeeDto) val body = client.toBlocking().retrieve(request) } catch (e: JsonSyntaxException) { log.error ("Error parsing json", e) } log.info("Employee creation request sent successfully") } }
初始测试代码
val httpClient = mockk<HttpClient>(relaxed = true) val blockingHttpClient = mockk<BlockingHttpClient>(relaxed = true) test("Employee listener test") { val listener = EmployeeListener(httpClient) val request: HttpRequest<Any> = HttpRequest.POST("/employee/create", EmployeeDto("Test1")) every { httpClient.toBlocking() } returns blockingHttpClient every { blockingHttpClient.retrieve(any()) } returns "{'name': 'Test1'}" listener.recieveEmployee("{'name': 'Test1'}") verify { httpClient.toBlocking().retrieve(request) } }
更新后的测试代码(仍失效)
@MicronautTest class EmployeeListenerTest: FunSpec( { val httpClient = mockk<HttpClient>(relaxed = true) val blockingHttpClient = mockk<BlockingHttpClient>(relaxed = true) test("Employee listener test") { val listener = EmployeeListener(httpClient) val request: HttpRequest<Any> = HttpRequest.POST("/employee/create", EmployeeDto("Test1")) every { httpClient.toBlocking().retrieve(any()) } returns "{'name': 'Test1'}" //every { blockingHttpClient.retrieve(any()) } returns "{'name': 'Test1'}" listener.recieveEmployee("{'name': 'Test1'}") verify { httpClient.toBlocking().retrieve(any()) } } })
问题原因
对象实例不匹配:测试代码中手动创建的
request对象,和EmployeeListener内部生成的request是完全独立的两个实例。默认情况下,HttpRequest的equals方法不会认为这两个对象相等,导致verify时用eq(request)匹配失败。Mock链式调用的处理问题:
- 初始测试中,虽然已经mock了
httpClient.toBlocking()返回blockingHttpClient,但verify { httpClient.toBlocking().retrieve(request) }这种链式调用的验证方式,Mockk无法正确关联到之前mock的blockingHttpClient实例,导致验证失效。 - 更新后的测试直接mock
httpClient.toBlocking().retrieve(any()),这种写法不符合Mockk对多层mock的处理逻辑,无法正确绑定到实际的调用链路。
- 初始测试中,虽然已经mock了
解决方案
方案1:修改验证逻辑,使用模糊匹配或属性匹配
放弃直接匹配request实例,改用any()匹配任意HttpRequest,或者通过属性校验确保请求符合预期:
// 方式1:匹配任意HttpRequest verify { blockingHttpClient.retrieve(any<HttpRequest<Any>>()) } // 方式2:校验请求的关键属性 verify { blockingHttpClient.retrieve( match { req -> req.method == HttpMethod.POST && req.path == "/employee/create" && (req.body as EmployeeDto).name == "Test1" } ) }
方案2:确保Mock链路的一致性
严格按照调用链路分层mock,并直接验证底层mock对象的方法调用:
test("Employee listener test") { val listener = EmployeeListener(httpClient) // 分层mock:先mock toBlocking()返回指定的blockingHttpClient every { httpClient.toBlocking() } returns blockingHttpClient // mock retrieve方法的返回值 every { blockingHttpClient.retrieve(any()) } returns "{'name': 'Test1'}" listener.recieveEmployee("{'name': 'Test1'}") // 直接验证blockingHttpClient的retrieve方法被调用 verify { blockingHttpClient.retrieve(any()) } }
方案3:重写equals方法(可选)
如果需要严格匹配EmployeeDto和HttpRequest,可以重写EmployeeDto的equals和hashCode方法,确保内容相同的对象能被判定为相等,这样测试中手动创建的request就能和Listener内部生成的request匹配成功。
内容的提问来源于stack exchange,提问作者Bharat Jain
相关产品推荐
相关产品推荐

