
本文旨在帮助初学者理解如何在单元测试中使用 Mock,特别是针对涉及第三方 API 调用和文件写入的场景。通过 WireMock 示例,展示了如何模拟不同响应码和响应体,以及如何验证请求头和 URL,从而编写更有效的单元测试。
在编写单元测试时,一个常见的挑战是如何处理外部依赖,例如第三方 API 调用或文件系统操作。直接依赖这些外部系统会导致测试不稳定、耗时,并且难以控制各种边界情况。这时,Mock 技术就显得尤为重要。
关键在于隔离被测单元。单元测试的目的是验证代码中的一个特定单元(例如一个方法或一个类)的行为是否符合预期。为了实现这一点,我们需要隔离这个单元,使其不受外部因素的影响。Mock 允许我们用可控的替代品替换这些外部依赖,从而实现隔离。
对于涉及第三方 API 调用的方法,例如以下代码:
public Object getAirQualityIndex(int id) {
try {
String stationInfoUrl = aqIndexUrlPattern.replace("{id}", String.valueOf(id));
HttpRequest stationRequest = HttpRequest.newBuilder()
.uri(URI.create(stationInfoUrl))
.GET()
.build();
HttpResponse<String> stationResponse = HttpClient.newBuilder()
.build()
.send(stationRequest, HttpResponse.BodyHandlers.ofString());
if (stationResponse.statusCode() != 200) {
throw new RuntimeException("Air Quality Index is currently unavailable, " +
"status code " + stationResponse.statusCode());
}
return new ObjectMapper().readValue(stationResponse.body(), new TypeReference<>() {});
} catch (Exception e) {
throw new RuntimeException("failed to get station measures information", e);
}
}使用真正的 API 调用进行测试会带来诸多问题:网络不稳定、API 服务不可用、测试速度慢等等。更好的方法是使用 WireMock 这样的工具来模拟 API 的行为。
WireMock 是一个强大的 HTTP 模拟服务器,可以让你定义模拟的 API 响应,并验证你的代码是否按照预期的方式与 API 交互。
以下是一个使用 WireMock 的示例:
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class AirQualityIndexTest {
private WireMockServer wireMockServer;
@BeforeEach
public void setup() {
wireMockServer = new WireMockServer(options().port(8080)); // 选择一个空闲端口
wireMockServer.start();
WireMock.configureFor("localhost", 8080);
}
@AfterEach
public void teardown() {
wireMockServer.stop();
}
@Test
public void testGetAirQualityIndex_Success() throws IOException, InterruptedException {
// 定义 WireMock 模拟的 API 响应
stubFor(get(urlEqualTo("/airquality/123"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("{\"aqi\": 50}")));
// 创建一个 HttpClient 并调用 API
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/airquality/123"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// 断言响应状态码和响应体
assertEquals(200, response.statusCode());
assertEquals("{\"aqi\": 50}", response.body());
// 验证 API 是否被调用过
verify(getRequestedFor(urlEqualTo("/airquality/123")));
}
@Test
public void testGetAirQualityIndex_ServerError() throws IOException, InterruptedException {
// 定义 WireMock 模拟的 API 响应 (服务器错误)
stubFor(get(urlEqualTo("/airquality/123"))
.willReturn(aResponse()
.withStatus(500)));
// 创建一个 HttpClient 并调用 API
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/airquality/123"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// 断言响应状态码
assertEquals(500, response.statusCode());
}
}在这个例子中,我们首先启动了一个 WireMock 服务器,并定义了两个模拟的 API 响应:一个成功的响应 (状态码 200) 和一个服务器错误的响应 (状态码 500)。然后,我们使用 HttpClient 调用 API,并断言响应状态码和响应体是否符合预期。最后,我们使用 verify 方法验证 API 是否被调用过。
通过使用 WireMock,我们可以完全控制 API 的行为,并测试各种边界情况,例如服务器错误、超时等等。
对于涉及文件写入的方法,例如:
public void writeAirQualityIndexAsPdf(Object aqIndex, Path destPath) throws IOException {
PdfDocument pdf = new PdfDocument(new PdfWriter(destPath.toFile()));
Document document = new Document(pdf);
String aqIndexYaml = new YAMLMapper()
.writerWithDefaultPrettyPrinter()
.withRootName("AirQualityIndex")
.writeValueAsString(aqIndex);
//replaced spaces with \u00A0 to prevent itext7 to trim whitespaces
document.add(new Paragraph(aqIndexYaml.replaceAll(" ", "\u00A0")));
document.close();
}我们可以使用 Mockito 等 Mock 框架来模拟 PdfWriter 和 Document 对象,从而避免实际的文件写入。 另一种方法是使用一个临时目录进行测试,然后在测试完成后删除该目录。
以下是一个使用临时目录的示例:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class AirQualityIndexWriterTest {
@Test
public void testWriteAirQualityIndexAsPdf(@TempDir Path tempDir) throws IOException {
// 创建一个临时文件
Path destPath = tempDir.resolve("air_quality_index.pdf");
// 创建一个 AirQualityIndex 对象 (这里假设你已经定义了这个类)
Object aqIndex = new Object(); // 替换为实际的 AirQualityIndex 对象
// 调用 writeAirQualityIndexAsPdf 方法
AirQualityIndexWriter writer = new AirQualityIndexWriter(); // 假设这个类包含 writeAirQualityIndexAsPdf 方法
writer.writeAirQualityIndexAsPdf(aqIndex, destPath);
// 断言文件是否被创建
assertTrue(Files.exists(destPath));
// (可选) 验证文件的内容是否符合预期
// 可以读取文件并进行断言
}
}在这个例子中,我们使用了 JUnit 5 的 @TempDir 注解来创建一个临时目录。这个临时目录会在测试方法执行完毕后自动删除。然后,我们创建了一个临时文件,并调用 writeAirQualityIndexAsPdf 方法将数据写入该文件。最后,我们断言文件是否被创建。
通过合理使用 Mock 技术,我们可以编写更有效、更可靠的单元测试。WireMock 可以帮助我们模拟 API 调用,Mockito 可以帮助我们模拟对象行为,而临时目录可以帮助我们测试文件系统操作。记住,Mock 的目的是隔离被测单元,关注行为而不是实现,并保持测试简洁。掌握这些原则,你就能编写出高质量的单元测试,提高代码的质量和可维护性。
以上就是使用 Mock 进行单元测试的正确姿势的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号