如何使用泛型Mock Micronaut的网络调用?
问题描述
我有一段通过Micronaut HTTP加载公钥的代码:
import io.micronaut.http.HttpRequest import io.micronaut.http.client.HttpClient class Mcve(val client: HttpClient) { fun loadKey(): String { val keyRequest = HttpRequest.GET<String>("https://example.org/key") val keyResponse = client.toBlocking().exchange(keyRequest, String::class.java) return keyResponse.body() } }
使用Mockk测试时,我不想发起真实网络请求,希望返回模拟密钥。我不想Mock被测类,更倾向于Mock HttpClient的调用,同时要避开Micronaut的DI框架(不使用@MicronautTest)。
我的测试代码如下:
import io.micronaut.http.HttpRequest import io.micronaut.http.client.BlockingHttpClient import io.micronaut.http.client.HttpClient import io.mockk.coEvery import io.mockk.mockk import org.junit.jupiter.api.Test import kotlin.test.assertEquals class McveTest { @Test fun usesDummyKey() { val keyClient: HttpClient = mockk(relaxed = true) val blockMock: BlockingHttpClient = mockk(relaxed = true) coEvery { keyClient.toBlocking() } returns blockMock coEvery { // blockMock.exchange(HttpRequest::class, String::class) // blockMock.exchange(HttpRequest<String>, String::class) // blockMock.exchange(ofType<HttpRequest<any()>>, String::class) blockMock.exchange(HttpRequest.GET(any()), String::class) } returns "dummy key" assertEquals("dummy key", Mcve(keyClient).loadKey()) } }
IntelliJ对blockMock.exchange代码报错:
None of the following functions can be called with the arguments supplied. exchange(HttpRequest<TypeVariable(I)!>!, Argument<TypeVariable(O)!>!) where I = TypeVariable(I), O = TypeVariable(O) for fun <I : Any!, O : Any!> exchange(request: HttpRequest<I!>!, bodyType: Argument<O!>!): HttpResponse<O!>! defined in io.micronaut.http.client.BlockingHttpClient exchange(HttpRequest<TypeVariable(I)!>!, Class<TypeVariable(O)!>!) where I = TypeVariable(I), O = TypeVariable(O) for fun <I : Any!, O : Any!> exchange(request: HttpRequest<I!>!, bodyType: Class<O!>!): HttpResponse<O!>! defined in io.micronaut.http.client.BlockingHttpClient exchange(String!, Class<TypeVariable(O)!>!) where O = TypeVariable(O) for fun <O : Any!> exchange(uri: String!, bodyType: Class<O!>!): HttpResponse<O!>! defined in io.micronaut.http.client.BlockingHttpClient
我尝试过相关的泛型重载匹配方案,但仍出现编译错误。请问如何Mockexchange调用,或是通过其他方式向loadKey传递模拟密钥?
解决方案
1. 修正HttpClient的Mock逻辑
关键问题分析
exchange方法返回的是HttpResponse<O>类型,而非直接返回字符串,原测试中直接返回"dummy key"存在类型不匹配问题。- 泛型重载方法的匹配需要精准指定类型,避免歧义。
- 原测试代码存在笔误:
keyService.toBlocking()应为keyClient.toBlocking()。
修正后的测试代码
import io.micronaut.http.HttpRequest import io.micronaut.http.HttpResponse import io.micronaut.http.client.BlockingHttpClient import io.micronaut.http.client.HttpClient import io.mockk.every import io.mockk.mockk import io.mockk.ofType import org.junit.jupiter.api.Test import kotlin.test.assertEquals class McveTest { @Test fun usesDummyKey() { val dummyKey = "dummy key" // Mock BlockingHttpClient,指定返回包含密钥的HttpResponse val blockMock: BlockingHttpClient = mockk() every { blockMock.exchange( ofType<HttpRequest<String>>(), String::class.java ) returns HttpResponse.ok(dummyKey) } // Mock HttpClient,让toBlocking返回上面的blockMock val keyClient: HttpClient = mockk() every { keyClient.toBlocking() } returns blockMock // 执行测试断言 assertEquals(dummyKey, Mcve(keyClient).loadKey()) } }
2. 更简洁的替代方案:提取依赖接口
如果觉得Mock HttpClient层级繁琐,可将密钥加载逻辑封装为独立接口,降低耦合度:
重构生产代码
// 定义密钥加载接口 interface KeyLoader { fun loadPublicKey(): String } // 基于Http的实现 class HttpKeyLoader(val client: HttpClient) : KeyLoader { override fun loadPublicKey(): String { val keyRequest = HttpRequest.GET<String>("https://example.org/key") val keyResponse = client.toBlocking().exchange(keyRequest, String::class.java) return keyResponse.body() } } // 被测类依赖KeyLoader接口 class Mcve(val keyLoader: KeyLoader) { fun loadKey(): String { return keyLoader.loadPublicKey() } }
对应的测试代码
import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.Test import kotlin.test.assertEquals class McveTest { @Test fun usesDummyKey() { val keyLoader: KeyLoader = mockk() every { keyLoader.loadPublicKey() } returns "dummy key" assertEquals("dummy key", Mcve(keyLoader).loadKey()) } }
这种方式完全避开了对HttpClient的复杂Mock,测试逻辑更清晰,也无需依赖Micronaut框架。
内容的提问来源于stack exchange,提问作者Robert
相关产品推荐
相关产品推荐

