Spring Boot 单元测试:如何为 RestTemplate 或 HTTP 客户端设置 Mock 行为 📅 2026/7/11 1:39:11 1. 引言在 Spring Boot 应用的单元测试中我们经常需要测试那些依赖外部 HTTP 服务的组件。直接调用真实的外部服务会带来网络延迟、服务不稳定、测试数据不可控等问题。这时为RestTemplate或WebClient等 HTTP 客户端设置 Mock 行为就成为了最佳实践。本文将详细介绍如何根据你的业务需求为RestTemplate或 HTTP 客户端设置 Mock 行为确保单元测试的隔离性、可重复性和高效性。2. 为什么需要 Mock HTTP 客户端在单元测试中我们关注的是被测组件自身的逻辑是否正确而不是外部服务的稳定性。Mock HTTP 客户端可以带来以下好处测试隔离不依赖外部网络和服务状态。可控的测试数据可以模拟各种成功、失败、超时等场景。测试速度避免了网络请求的耗时。可重复性每次测试都能获得相同的响应。3. 为 RestTemplate 设置 Mock 行为RestTemplate是 Spring 传统的同步 HTTP 客户端。我们可以使用MockRestServiceServer来 Mock 其行为。3.1 添加依赖确保你的项目中包含了 Spring Boot Test 依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency3.2 基本 Mock 示例以下是一个完整的单元测试示例展示了如何为RestTemplate设置 Mock 行为import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestTemplate; import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; import static org.springframework.test.web.client.response.MockRestResponseCreators.*; SpringBootTest public class UserServiceTest { Autowired private RestTemplate restTemplate; Autowired private UserService userService; Test public void testGetUserById() { // 创建 Mock 服务器 MockRestServiceServer mockServer MockRestServiceServer.createServer(restTemplate); // 设置 Mock 行为当请求 GET /users/123 时返回成功的 JSON 响应 mockServer.expect(requestTo(http://api.example.com/users/123)) .andExpect(method(HttpMethod.GET)) .andRespond(withSuccess( {\id\:123,\name\:\张三\,\email\:\zhangsanexample.com\}, MediaType.APPLICATION_JSON )); // 执行被测方法 User user userService.getUserById(123); // 验证 Mock 请求是否按预期执行 mockServer.verify(); // 断言业务逻辑 assertThat(user.getId()).isEqualTo(123); assertThat(user.getName()).isEqualTo(张三); } Test public void testGetUserById_NotFound() { MockRestServiceServer mockServer MockRestServiceServer.createServer(restTemplate); // 模拟 404 响应 mockServer.expect(requestTo(http://api.example.com/users/999)) .andExpect(method(HttpMethod.GET)) .andRespond(withStatus(HttpStatus.NOT_FOUND)); // 执行并验证异常 assertThrows(UserNotFoundException.class, () - { userService.getUserById(999); }); mockServer.verify(); } }3.3 高级 Mock 配置你还可以根据业务需求设置更复杂的 Mock 行为// 1. 模拟延迟响应 mockServer.expect(requestTo(/api/data)) .andExpect(method(HttpMethod.GET)) .andRespond(withSuccess() .body({\status\:\processing\}) .contentType(MediaType.APPLICATION_JSON) .delay(Duration.ofSeconds(2))); // 2秒延迟 // 2. 验证请求头 mockServer.expect(requestTo(/api/protected)) .andExpect(method(HttpMethod.GET)) .andExpect(header(Authorization, Bearer token123)) .andRespond(withSuccess()); // 3. 验证请求体POST/PUT mockServer.expect(requestTo(/api/users)) .andExpect(method(HttpMethod.POST)) .andExpect(content().json({\name\:\李四\,\age\:30})) .andRespond(withCreatedEntity(URI.create(/api/users/456))); // 4. 模拟网络错误 mockServer.expect(requestTo(/api/unstable)) .andExpect(method(HttpMethod.GET)) .andRespond(withException(new IOException(Connection timeout)));4. 为 WebClient 设置 Mock 行为对于响应式的WebClient我们可以使用MockWebServer来自 OkHttp或WireMock。4.1 使用 MockWebServerimport okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.MockResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.web.reactive.function.client.WebClient; import reactor.test.StepVerifier; public class ReactiveUserServiceTest { private MockWebServer mockWebServer; private WebClient webClient; BeforeEach void setUp() throws IOException { mockWebServer new MockWebServer(); mockWebServer.start(); webClient WebClient.builder() .baseUrl(mockWebServer.url(/).toString()) .build(); } AfterEach void tearDown() throws IOException { mockWebServer.shutdown(); } Test void testGetUser() { // 设置 Mock 响应 mockWebServer.enqueue(new MockResponse() .setResponseCode(200) .setHeader(Content-Type, application/json) .setBody({\id\:1,\name\:\王五\})); // 执行请求 MonoUser userMono webClient.get() .uri(/users/1) .retrieve() .bodyToMono(User.class); // 验证 StepVerifier.create(userMono) .expectNextMatches(user - user.getId() 1 王五.equals(user.getName())) .verifyComplete(); // 验证请求 RecordedRequest request mockWebServer.takeRequest(); assertThat(request.getPath()).isEqualTo(/users/1); assertThat(request.getMethod()).isEqualTo(GET); } }4.2 使用 WireMockWireMock 是一个更强大的 HTTP Mock 服务器支持更复杂的场景import com.github.tomakehurst.wiremock.client.WireMock; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; import static com.github.tomakehurst.wiremock.client.WireMock.*; SpringBootTest(webEnvironment SpringBootTest.WebEnvironment.RANDOM_PORT) AutoConfigureWireMock(port 8089) public class WireMockUserServiceTest { Autowired private UserService userService; Test public void testGetUserWithWireMock() { // 设置 WireMock 桩 stubFor(get(urlEqualTo(/users/100)) .willReturn(aResponse() .withStatus(200) .withHeader(Content-Type, application/json) .withBody({\id\:100,\name\:\赵六\}))); // 执行测试 User user userService.getUserById(100); // 验证 assertThat(user.getId()).isEqualTo(100); assertThat(user.getName()).isEqualTo(赵六); // 验证请求是否发送到 WireMock verify(getRequestedFor(urlEqualTo(/users/100))); } }5. 最佳实践与注意事项5.1 根据业务场景选择 Mock 策略简单内部服务调用使用MockRestServiceServerRestTemplate或MockWebServerWebClient。复杂外部 API 模拟使用 WireMock支持请求匹配、响应模板、状态机等高级功能。契约测试考虑使用 Spring Cloud Contract确保消费者和提供者之间的契约一致性。5.2 测试数据管理将测试数据外部化便于维护// 使用 Value 或资源文件加载测试数据 Value(classpath:test-data/user-success.json) private Resource userSuccessResource; Test public void testWithExternalData() throws IOException { String responseBody new String(userSuccessResource.getInputStream().readAllBytes()); mockServer.expect(requestTo(/api/users/1)) .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); // ... 测试逻辑 }5.3 清理与重置在每个测试方法后清理 Mock 状态避免测试间相互影响AfterEach public void tearDown() { // 对于 MockRestServiceServer if (mockServer ! null) { mockServer.verify(); mockServer.reset(); // 重置期望 } // 对于 WireMock WireMock.reset(); }5.4 验证请求次数确保 HTTP 客户端按预期次数调用// 验证只调用一次 mockServer.expect(once(), requestTo(/api/data)) .andRespond(withSuccess()); // 验证调用两次 mockServer.expect(times(2), requestTo(/api/data)) .andRespond(withSuccess()); // 验证最多调用一次 mockServer.expect(atMostOnce(), requestTo(/api/data)) .andRespond(withSuccess());6. 常见问题与解决方案6.1 Mock 不生效可能原因RestTemplate/WebClient 不是通过 Spring 容器注入的自己 new 的实例测试类没有正确的注解如SpringBootTestURL 不匹配注意协议、域名、端口、路径解决方案// 确保使用同一个 RestTemplate 实例 Configuration public class TestConfig { Bean public RestTemplate restTemplate() { return new RestTemplate(); } } // 在测试中注入 Autowired private RestTemplate restTemplate;6.2 如何 Mock 第三方库内部的 HTTP 调用如果第三方库内部创建了自己的 HTTP 客户端可以考虑使用 PowerMock/Mockito 来 Mock 静态方法或构造函数将第三方库包装一层测试自己的包装类使用真正的 Mock 服务器如 WireMock并配置代理6.3 性能测试中的 Mock对于性能测试简单的 Mock 可能不够使用MockRestServiceServer的andRespond()方法设置延迟使用 WireMock 的fixedDelay()或randomDelay()考虑使用专门的性能测试工具如 Gatling、JMeter7. 总结根据你的业务需求为 HTTP 客户端设置 Mock 行为是编写高质量单元测试的关键。本文介绍了使用MockRestServiceServerMockRestTemplate使用MockWebServer或 WireMock MockWebClient各种业务场景下的 Mock 配置示例最佳实践和常见问题解决方案选择适合你项目的 Mock 策略可以让单元测试更加可靠、快速和可维护。记住好的 Mock 不是让测试通过而是让测试真实反映业务逻辑的正确性。