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

如何使用泛型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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 16:30:56