Spring Boot 单元测试:如何为 RestTemplate 或 HTTP 客户端设置 Mock 行为
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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
3.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\":\"zhangsan@example.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 使用 MockWebServer
import 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\":\"王五\"}"));
// 执行请求
Mono<User> 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 使用 WireMock
WireMock 是一个更强大的 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 策略
- 简单内部服务调用:使用
MockRestServiceServer(RestTemplate)或MockWebServer(WebClient)。 - 复杂外部 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 的实例)
- 测试类没有正确的注解(如
@SpringBootTest) - URL 不匹配(注意协议、域名、端口、路径)
解决方案:
// 确保使用同一个 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、JMeter)
7. 总结
根据你的业务需求为 HTTP 客户端设置 Mock 行为,是编写高质量单元测试的关键。本文介绍了:
- 使用
MockRestServiceServerMockRestTemplate - 使用
MockWebServer或 WireMock MockWebClient - 各种业务场景下的 Mock 配置示例
- 最佳实践和常见问题解决方案
选择适合你项目的 Mock 策略,可以让单元测试更加可靠、快速和可维护。记住:好的 Mock 不是让测试通过,而是让测试真实反映业务逻辑的正确性。
更多推荐

所有评论(0)