如何模拟HttpClient和HttpRequest在kotlin单元测试?



我已经构建了一个spring引导应用程序,它有一个名为FileDetails.kt的类。我想测试的方法,在类中,是在以下格式:

getFileDetails(auth: String, id: String): FileModel {
val url = URI.create(“http://localhost:8080/$id”)
val client = HttpClient.newBuilder().build()
val request = HttpRequest.newBuilder().uri(url).header(“Authorization”, auth).build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
return objectMapper.readValue(response.body(), object: TypeReference<FileModel>() {})}

我正在编写一个单元测试来模拟,当响应代码为200时,我们得到一个FileModel响应。这是我到目前为止所做的,但不确定如何继续。

@Test fun `test get file details method`() {
val response200 = Mockito.mock(HttpResponse::class.java)
val mockRequest = Mockito.mock(HttpRequest::class.java)
// not sure how to return mockRequest and response200}

我是相当新的,想知道是否有可能模拟这些反应,以及如何去做。

你做错了。

如果您的目标只是模拟getFileDetails(),那么这很容易,您根本不需要为HttpResponse而烦恼。首先,假设你的获取器在一个类中,像这样:

class Fetcher {
fun getFileDetails(auth: String, id: String): FileModel {
val url = URI.create("http://localhost:8080/$id")
val client = HttpClient.newBuilder().build()
val request = HttpRequest.newBuilder().uri(url).header("Authorization", auth).build()
return objectMapper.readValue(response.body(), object: TypeReference<FileModel>() {})}
}
}

你只需写

(对不起,我使用https://mockk.io/而不是Mockito,因为Mockk是专门为Kotlin设计的mock库,我更了解它-但概念/方法是相同的),
import io.mockk.every
import io.mockk.mockk
val fetcher = mockk<Fetcher>()
every { fetcher.fetchRawResponse(any(), any()) } returns FileModel(...)

如果你真的想在较低的层次上测试HttpRequest/Response,你需要分解getFileDetails(依赖注入风格)。你想测试的是什么?

  1. HTTP请求是否正确形成?
  2. 从String到FileModel的反序列化是正确的?

假设它只是(2)你想测试-我会避免测试任何Http的东西,并假设HttpClient的作者已经正确地测试了他们的代码,并抽象你的外部调用像这样:

class Fetcher {
fun fetchRawResponse(auth: String, id: String) : HttpResponse<String> {
val url = URI.create("http://localhost:8080/$id")
val client = HttpClient.newBuilder().build()
val request = HttpRequest.newBuilder().uri(url).header("Authorization", auth).build()
return client.send(request, HttpResponse.BodyHandlers.ofString())
}
}

然后调用者可以执行objectMapper.readValue(甚至更进一步,通过返回一个普通的String,不泄漏HttpResponse对象)

假设您有一个像上面那样的Fetcher类,这就是如何使用mock响应。

import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
@Test fun `test get file details method`() {
val jsonResponse: HttpResponse<String> = mockk()
every { jsonResponse.body() } returns  """
{"some":"json"}
""".trimIndent()
val fetcher = mockk<Fetcher>()
every { fetcher.fetchRawResponse(any(), any()) } returns jsonResponse
// now you can call fetcher.fetchRawResponse("abc","def")
}

您可以将mock的静态mock和捕获功能结合起来,以验证例如POST请求有效负载等:

import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.slot
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpRequest.BodyPublishers
import java.net.http.HttpResponse
import java.nio.charset.StandardCharsets
val httpClient = mockk<HttpClient>()
val httpResponse = mockk<HttpResponse<String>>()
every { httpResponse.body() } returns "ignored"
every { httpResponse.statusCode() } returns 200
val httpRequestCapturingSlot = slot<HttpRequest>()
every {
httpClient.send(
capture(httpRequestCapturingSlot),
HttpResponse.BodyHandlers.ofString()
)
} returns httpResponse
val expectedRequestJson = Gson().toJson(
<your expected payload object>
)
mockkStatic(BodyPublishers::class)
val postBodyCapturingSlot = slot<String>()
every { BodyPublishers.ofString(capture(postBodyCapturingSlot)) } returns BodyPublishers.ofString(
"stand-in only",
StandardCharsets.UTF_8
)
// make SUT call resulting in HttpRequest
val contentHeader = "Content-Type" to listOf("application/json")
val httpRequest = httpRequestCapturingSlot.captured
httpRequest.method() shouldBe "POST"
httpRequest.uri().toString() shouldBe <expected uri>
httpRequest.headers().map() shouldContain contentHeader
postBodyCapturingSlot.captured shouldBe expectedRequestJson

最新更新