我已经构建了一个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
(依赖注入风格)。你想测试的是什么?
- HTTP请求是否正确形成?
- 从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