ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Spring Boot RestClient单元测试实战:MockRestServiceServer与WireMock方案解析

Spring Boot RestClient单元测试实战:MockRestServiceServer与WireMock方案解析 1. 先别急着写测试搞清楚 RestClient 到底是什么1.1 版本号这件事6.1 其实是 Spring Framework 的版本看到标题里写着“Spring Boot 6.1”估计不少人和我一开始一样愣了一下因为 Spring Boot 的版本号目前主线是 3.x而 6.1 是 Spring Framework 的版本号Spring Boot 3.2 开始就是基于 Spring Framework 6.1 的。所以“Spring Boot 6.1”这个说法如果理解成“Spring Framework 6.1 Spring Boot 3.2”在技术上是完全成立的RestClient 正是 Spring Framework 6.1 新增的同步 HTTP 客户端。这个细节不是抠字眼它直接影响你查文档、搜资料、引依赖时的准确性。我在工作中遇到过好几个同事在 Maven 里找spring-boot-starter-restclient找了半天发现没有这个 starter因为 RestClient 不在单独的 starter 里它跟着spring-web模块走。Spring Boot 3.2 的spring-boot-starter-web、spring-boot-starter-webflux甚至spring-boot-starter-web-services里都有它但如果你用的是 WebFlux 的 starter又想要同步的 RestClient 行为那就得额外引spring-web的依赖这里面的细微差别踩坑的人不少。RestClient 的定位非常清晰它要替代的是 RestTemplate。RestTemplate 从 Spring 5.0 开始就被官方标记为维护模式不会再加新功能了但团队里用它的老项目又特别多因为大家习惯了它那种同步、直接、写起来不绕弯的风格。WebClient 虽然功能强大、异步非阻塞但学习成本确实高对于只想要“发一个 GET 请求拿个 JSON 回来”这种场景WebClient 显得有点杀鸡用牛刀。RestClient 就是在这个背景下出现的保留 RestTemplate 那种直观的同步编码体验同时把 API 设计得更加链式、流畅把 UriBuilder、ExchangeFilterFunction、消息转换器这些 WebClient 的优点也吸收了过来。1.2 RestClient 与 RestTemplate、WebClient 怎么选很多朋友问我一个问题我现在要新写一个服务间调用到底选哪个我给团队定的参考思路是这样的维度RestTemplateWebClientRestClient编码风格命令式简单直接响应式链式回调/流式命令式 链式最接近现代写法异步支持不支持传统同步阻塞完美支持非阻塞暂不支持异步纯同步学习成本低中高低与 Spring 生态契合度低进入维护模式高高官方主推适合场景老项目维护高并发、网关、流式调用绝大多数服务间同步调用这里多说一句异步的事。RestClient 本身是同步设计但 Spring 官方在 6.1 里同时提供了RestClient的响应式兄弟RestClient的 WebClient 变体即用 RestClient 的链式语法包着 WebClient 实现不过日常用不到咱不展开。我的判断标准很朴素如果你的接口调用方本身不是高性能网关也没有流式推送需求就用 RestClient它写出来的代码最好读、最好维护。1.3 为什么单元测试这个主题值得单拎出来写说实话我见过太多团队使用 RestTemplate 或 RestClient 时测试只有两种状态要么不写要么写一个 mock 掉整个 Service 层的“假单元测试”真正到了 HTTP 客户端这一层就裸奔了。等哪天第三方接口联调出问题线上日志打出来的错误是ConnectTimeoutException还是ResourceAccessException好多人分不清楚更别提怎么在测试里模拟出这两种不同的超时场景。RestClient 引入的新 API 刚出来一年左右网上关于它的单元测试文章不少但质量参差不齐有些直接照搬 WebClient 的测试方案有些用的还是旧版RestTemplate的过时 API照着抄很容易掉坑里。这篇文章我会从方案选型、代码实现、常见报错三个层面把 RestClient 单元测试这件事掰开揉碎讲清楚项目代码也是我自己在 Spring Boot 3.2.4 基础上跑通的可以放心抄。2. 设计方案单测 RestClient 的 3 种主流思路2.1 思路一MockRestServiceServer 轻量模拟MockRestServiceServer是 Spring 官方专门为 RestTemplate 提供的测试工具在 Spring Framework 6.1 之后它同步支持对 RestClient 的 mock。这个方案的核心思路非常朴素你家的服务要去请求第三方接口测试里我不想真去请求那我就给这个“网络请求”套一个代理测试代码里预先定义好“如果收到某个 URL 的 GET 请求就返回指定的 JSON 字符串”。它最大的优点是简单、快速、没有额外依赖也不需要起一个真实的 socket 端口。缺点也明显它 mock 的是底层 request factory而不是真实网络栈所以它测不到 DNS 解析、连接池、真实超时这些网络层面的问题。但作为单元测试这完全够用了因为你本来就不想在单测里真的发起外部调用。实现方式上用MockRestServiceServer.bindTo(RestClient.Builder)把测试服务绑定到 builder 上然后在测试中server.expect(...)写期望的请求和返回。这里有一个从 RestTemplate 迁移过来的注意点RestTemplate 时代调用MockRestServiceServer.createServer(restTemplate)就够了但 RestClient 因为采取了 builder 模式你需要把 server 绑定到 builder 再让 builder 构建出 RestClient否则测试里 mock 不生效。2.2 思路二WireMock 模拟真实 HTTP 服务如果你希望测试尽可能接近真实 HTTP 调用比如要验证 header 里的认证信息、要模拟第三方服务返回 500/超时等异常场景WireMock 是更好的选择。它本质上是在测试进程里启动一个真正的 HTTP 服务器然后你的 RestClient 像访问真实服务一样访问这个本地端口。WireMock 的优势是“真”RestClient 从连接建立、发送请求、读取响应到解析 JSON 的完整链路都会执行一遍如果底层配置有问题比如连接池设置、超时逻辑用 WireMock 能暴露出来。缺点是测试启动稍慢、需要管理端口和生命周期如果项目里所有单测都用它CI 时长会明显增加。这两套方案怎么取舍我的经验是纯 Service 层的单测用 MockRestServiceServer涉及 HTTP 协议细节的集成测试用 WireMock。还有一种更“重”的方案是用 Testcontainers 起一个容器化的 MockServer但那种一般留给端到端测试单测阶段引入容器反而增加了不稳定性不建议一上来就这么豪横。2.3 要不要拉起来 Spring 容器关于这个问题我见过两种极端一种人写单测动不动SpringBootTest把所有 Bean 全部加载测一个小 Client 要等 30 秒另一种人连容器都不碰纯手动 new 对象但遇到 RestClient.Builder 注入、依赖别的配置类时又无从下手。我的建议是分场景如果只是测一个简单的 Client 方法、验证 URL/header/body完全不需要启动 Spring 容器手动RestClient.builder()构建然后绑定 MockRestServiceServer 即可。但如果你配置了自定义的ClientHttpRequestInterceptor、Jackson消息转换器或者依赖了配置文件的某些属性那就用SpringBootTestAutoConfigureMockRestServiceServer让 Spring 帮你把 builder 注入好测试只关注业务断言这更符合“集成测试”的定位。2.4 先写测试还是先写功能代码国内团队对这个问题的态度两极分化要么严格执行 TDD要么完全跳过测试。我自己的体会是RestClient 这种外部调用代码先写测试的价值比普通 CRUD 代码更大因为它要对接的接口是你不可控的第三方你必须在写业务代码前就确定好请求格式、响应结构、异常处理逻辑。你先写一个“我想让某个请求返回什么数据”的测试相当于先把协议定下来再写实现就不容易跑偏。我常用的节奏是先定义接口 DTO再写一个空实现然后写测试用例正常返回、404、500、超时四种情况最后补实现代码让测试变绿。这样做的好处是你会在写测试的过程中发现很多设计上的问题比如响应的 JSON 有嵌套字段你没考虑到、有些 header 是动态生成的、第三方接口分页结构和你预期不一致这些问题提前暴露比联调时暴露成本低得多。3. 实操过程MockRestServiceServer 方案一步步落地3.1 准备环境和依赖先交代一下工程环境JDK 17Spring Boot 3.x 的最低要求、Spring Boot 3.2.4、Spring Framework 6.1.5、Maven 3.9。JDK 8 的老项目想用 RestClient 就不用想了升 JDK 17 是硬前提。Maven 依赖不需要额外的测试库直接用 spring-boot-starter-testdependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency注意如果项目里没引spring-boot-starter-web只引了spring-boot-starter-webflux那么 RestClient 类在spring-web模块中已经有了但有些 HTTP 客户端工厂比如 JDK HttpURLConnection 的默认实现在 WebFlux 环境下的测试行为会有差异建议还是统一用 web starter省得排查半天。3.2 先写一个待测的 Client 类假设我们正在做一个“上门烹饪预约服务”后端其中一个功能是调用外部菜单服务获取厨师信息。这个场景取自真实业务很能体现 RestClient 的使用方式。package com.example.cooking.client; import com.example.cooking.dto.ChefDto; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.client.RestClient; import java.util.List; Component public class ChefServiceClient { private final RestClient restClient; public ChefServiceClient(Qualifier(chefServiceRestClient) RestClient restClient) { this.restClient restClient; } public ChefDto getChefById(Long id) { return restClient.get() .uri(/api/chefs/{id}, id) .accept(MediaType.APPLICATION_JSON) .retrieve() .body(ChefDto.class); } public ListChefDto listChefsByCity(String city) { return restClient.get() .uri(uriBuilder - uriBuilder .path(/api/chefs) .queryParam(city, city) .build()) .accept(MediaType.APPLICATION_JSON) .retrieve() .body(new ParameterizedTypeReferenceListChefDto() {}); } }这里的Qualifier(chefServiceRestClient)是为了区分不同的 RestClient Bean当系统里同时有多个服务需要调用时不要都在同一个 RestClient 上叠 baseUrl给每个下游服务一个独立配置的 Client 是更清晰的设计。3.3 配置 RestClient 的 Bean在Configuration类中定义 RestClient Bean并设置连接超时和读取超时package com.example.cooking.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.JdkClientHttpRequestFactory; import org.springframework.web.client.RestClient; import java.net.http.HttpClient; import java.time.Duration; Configuration public class RestClientConfig { Bean(chefServiceRestClient) public RestClient chefServiceRestClient( Value(${chef.service.base-url}) String baseUrl) { HttpClient httpClient HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(3)) .build(); JdkClientHttpRequestFactory requestFactory new JdkClientHttpRequestFactory(httpClient); requestFactory.setReadTimeout(Duration.ofSeconds(5)); return RestClient.builder() .baseUrl(baseUrl) .requestFactory(requestFactory) .defaultHeader(X-Request-Source, cooking-platform) .build(); } }这里我选了 JDK 自带的 HttpClient 作为底层实现如果你想用 Apache HttpClient 5.x需要额外引入依赖并配置连接池但单测场景没有区别。注意JdkClientHttpRequestFactory在启动时就会创建一个HttpClient如果项目中还有复杂的 HTTP/2 或连接池配置需求建议换成HttpComponentsClientHttpRequestFactory。3.4 核心写 MockRestServiceServer 测试类终于写到最关键的部分了。由于 RestClient 使用了 builder 模式MockRestServiceServer 绑定时有特殊要求我一开始直接套用了 RestTemplate 时代的写法结果发现 mock 根本拦截不到请求后来仔细看了官方文档才明白问题出在哪。正确的完整测试代码如下package com.example.cooking.client; import com.example.cooking.dto.ChefDto; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestClient; import java.util.Arrays; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; import static org.springframework.test.web.client.response.MockRestResponseCreators.*; class ChefServiceClientTest { private MockRestServiceServer mockServer; private ChefServiceClient client; BeforeEach void setUp() { RestClient.Builder builder RestClient.builder() .baseUrl(http://chef-service.internal); mockServer MockRestServiceServer.bindTo(builder).build(); client new ChefServiceClient(builder.build()); } Test void testGetChefById_whenThirdPartyReturnsData_shouldDeserializeOk() { // given String json { id: 1001, name: 张师傅, specialty: 川菜, rating: 4.8 } ; mockServer.expect(requestTo(/api/chefs/1001)) .andExpect(method(HttpMethod.GET)) .andExpect(header(X-Request-Source, cooking-platform)) .andRespond(withSuccess(json, MediaType.APPLICATION_JSON)); // when ChefDto result client.getChefById(1001L); // then assertThat(result.getId()).isEqualTo(1001L); assertThat(result.getName()).isEqualTo(张师傅); assertThat(result.getSpecialty()).isEqualTo(川菜); mockServer.verify(); } Test void testListChefsByCity_whenNoData_shouldReturnEmptyList() { // given mockServer.expect(requestToUriStartingWith(/api/chefs?city上海)) .andExpect(method(HttpMethod.GET)) .andRespond(withSuccess([], MediaType.APPLICATION_JSON)); // when ListChefDto result client.listChefsByCity(上海); // then assertThat(result).isEmpty(); mockServer.verify(); } }这个测试类有个很重要的细节MockRestServiceServer.bindTo(builder).build()会在底层包装 builder之后任何通过这个 builder 创建的 RestClient 请求都会被它拦截。verify()放在最后断言所有声明的期望请求都被匹配到一个都不能多一个也不能少。3.5 异常场景怎么测RestClient 默认继承了 RestTemplate 的异常处理方式4xx 抛出HttpClientErrorException5xx 抛出HttpServerErrorException连接失败抛出ResourceAccessException。这些异常场景在测试中很容易模拟Test void testGetChefById_whenServerReturns500_shouldThrowHttpServerErrorException() { mockServer.expect(requestTo(/api/chefs/2000)) .andExpect(method(HttpMethod.GET)) .andRespond(withServerError()); assertThatThrownBy(() - client.getChefById(2000L)) .isInstanceOf(HttpServerErrorException.class) .hasMessageContaining(500); } Test void testGetChefById_whenThirdPartyTimeout_shouldThrowResourceAccessException() { // 通过 withException 模拟连接异常 mockServer.expect(requestTo(/api/chefs/3000)) .andExpect(method(HttpMethod.GET)) .andRespond(withException(new ConnectTimeoutException(connect timed out))); assertThatThrownBy(() - client.getChefById(3000L)) .isInstanceOf(ResourceAccessException.class) .hasMessageContaining(connect timed out); }这里我想强调一个容易混淆的概念读超时Read Timeout和连接超时Connect Timeout是两个不同的异常MockRestServiceServer 单测里能模拟的是“连接层”的异常因为请求根本没发出去。如果你想验证读超时的处理逻辑得用 WireMock 或 Testcontainers 起真实服务然后让服务端故意睡眠超过读超时时间这个在线上排查特别有用。3.6 配合 SpringBootTest 的写法如果项目里配置比较多想直接把测试环境拉起来可以用下面的方式SpringBootTest AutoConfigureMockRestServiceServer class ChefServiceClientIntegrationTest { Autowired private ChefServiceClient client; Autowired private MockRestServiceServer mockServer; Test void testGetChefById_withSpringContext() { mockServer.expect(requestTo(/api/chefs/1001)) .andRespond(withSuccess({\id\:1001,\name\:\李师傅\}, MediaType.APPLICATION_JSON)); ChefDto result client.getChefById(1001L); assertThat(result.getName()).isEqualTo(李师傅); mockServer.verify(); } }用AutoConfigureMockRestServiceServer时Spring Boot 会自动替换容器里的 RestClient.Builder 为 mock 版你的测试类不需要有任何绑定逻辑。但这个方案要求被注入的 RestClient 确实是项目中配置的同一个 builder 构建的如果代码里new RestClient那神仙也拦不住。4. WireMock 方案实战连协议栈一起测4.1 什么时候必须用 WireMock年初我们团队对接了一个第三方“菜品识别服务”对方文档没写好响应里有时候返回 JSON有时候返回纯文本还有时候返回的 JSON 字段名大小写不一致。MockRestServiceServer 这种“模拟”方式在这种场景下就使不上劲了因为不管真实服务返回什么MockRestServiceServer 给你的 response 都是你定义的字符串它测不到 RestClient 和消息转换器之间的协商过程。这时候把请求打到真实服务上、然后让真实服务“听你指挥”才是检验正确性的唯一标准。WireMock 干的就是这个事。4.2 引入 WireMock 并编写测试Maven 添加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency dependency groupIdcom.github.tomakehurst/groupId artifactIdwiremock-standalone/artifactId version3.3.1/version scopetest/scope /dependency测试代码package com.example.cooking.client; import com.example.cooking.dto.ChefDto; 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 org.springframework.http.MediaType; import org.springframework.web.client.RestClient; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.assertj.core.api.Assertions.assertThat; class ChefServiceClientWireMockTest { private WireMockServer wireMockServer; private ChefServiceClient client; BeforeEach void setUp() { wireMockServer new WireMockServer(8089); wireMockServer.start(); WireMock.configureFor(localhost, 8089); RestClient restClient RestClient.builder() .baseUrl(http://localhost:8089) .build(); client new ChefServiceClient(restClient); } AfterEach void tearDown() { wireMockServer.stop(); } Test void testGetChefById_withWireMock() { stubFor(get(urlEqualTo(/api/chefs/1001)) .willReturn(aResponse() .withStatus(200) .withHeader(Content-Type, application/json) .withBody({\id\:1001,\name\:\王师傅\,\specialty\:\粤菜\,\rating\:4.9}))); ChefDto result client.getChefById(1001L); assertThat(result.getName()).isEqualTo(王师傅); verify(getRequestedFor(urlEqualTo(/api/chefs/1001))); } }WireMock 跑起来之后RestClient 发请求就像打真实服务一样DNS、端口、连接、HTTP 协议全过程都会走一遍这能暴露一些 MockRestServiceServer 永远发现不了的问题。比如我们团队之前就遇到过ChefDto里有个LocalDateTime字段普通 JSON 序列化没问题但第三方服务返回的是yyyy-MM-dd HH:mm:ss这种非 ISO 格式在 MockRestServiceServer 里因为 response 直接走消息转换器解析根本没报警WireMock 下同样的 JSON 结果解析报错才暴露出问题最后加了JsonFormat注解才解决。4.3 在单元测试里模拟延迟和超时刚才提到的读超时场景WireMock 很容易模拟Test void testReadTimeout() { stubFor(get(urlEqualTo(/api/chefs/slow)) .willReturn(aResponse() .withFixedDelay(6000) // 人为延迟 6 秒 .withBody({}))); RestClient timeoutClient RestClient.builder() .baseUrl(http://localhost:8089) .requestFactory(new JdkClientHttpRequestFactory( HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(3)) .build())) .build(); // 需要单独设置读超时 JdkClientHttpRequestFactory factory (JdkClientHttpRequestFactory) timeoutClient.getRequestFactory(); factory.setReadTimeout(Duration.ofSeconds(2)); assertThatThrownBy(() - timeoutClient.get() .uri(/api/chefs/slow) .retrieve() .toBodilessEntity()) .isInstanceOf(ResourceAccessException.class) .hasMessageContaining(Read timed out); }有一点要提醒JdkClientHttpRequestFactory的读超时必须在构建RestClient之后单独设置如果一开始就在HttpClient构建时设置connectTimeout那只影响连接阶段不影响读取阶段。这个坑我遇到过不止一次线上排查超时问题时要分清楚是连接超时还是读超时。5. 常见问题与排查技巧实录5.1 报了Unexpected request是什么原因怎么定位这是 MockRestServiceServer 最经典的报错意思是有请求发出来了但你测试里没有对这次请求做过期望声明或者期望声明的 URL/method 对不上。最常见的原因是 RestClient 实际请求的 URL 和你预期的不一致。排查技巧把异常堆栈完整拉出来里面会带着实际请求的 method 和 URI对照你测试里expect的 matcher 逐项核对。另外特别提醒 URL 里的 query 参数requestTo(/api/chefs?city上海)这种写法对中文参数和你预期不一致时很容易误判建议用queryParam逐项匹配而不是拼字符串。5.2 RestClient 类找不到或者版本不兼容RestClient是 Spring 6.1 引入的类如果你用的 Spring Boot 3.1 或更早即使引了spring-web类也是不存在的。这时候 IDE 会报ClassNotFoundException或编译错误别纠结直接升 Spring Boot 到 3.2。还有一个很少人注意的点如果项目里手动指定了 Spring Framework 版本开发环境 Spring Boot 用的 6.1.x但内部某个模块锁定了 6.0.x那也会出现类找不到Maven 依赖树里查一下spring-web的版本即可。5.3 MockRestServiceServer mock 没生效请求打到了真实服务发生这种情况九成是 RestClient 实例不是你绑定后那个 builder 创建的。仔细检查一下MockRestServiceServer.bindTo(builder).build()之后你的被测类是否用的是这个 builder.build()出来的 RestClient如果被测类自己内部RestClient.create()或者 new 了一个那 mock 自然拦不到。另外检查是否多个RestClientBean 有同名的质量问题Qualifier指定错了也会导致注入的不是同一个实例。5.4 JSON 反序列化报错但接口文档明明没错使用 RestClient 时./body()的解析依赖消息转换器自动协商 content-type。如果第三方返回的Content-Type是application/json;charsetUTF-8没问题但如果第三方服务不规范返回了text/plain或者根本没有 Content-TypeRestClient 会认为响应不是 JSON走默认的消息转换器结果自然是反序列化失败或者拿到一个 String。解决办法在 Client 的请求代码里显式指定.accept(MediaType.APPLICATION_JSON)并且与第三方约定响应头必须带application/json。这个看着是小事但排查起来最容易让人怀疑人生我见过有人在这个问题上卡了两天。5.5 测试用例之间互相影响如果你在同一个测试类里写了多个测试方法且都用BeforeEach绑定 MockRestServiceServer那么每个测试方法运行前都会新建一个 server 实例用例之间互不干扰。但如果用了SpringBootTest且测试类标注Transactional事务会贯穿整个测试此时第三方服务调用不会被事务回滚影响别指望 mock 会在事务结束后自动恢复需要在BeforeEach里手动重置 server。用 WireMock 时多个测试类并行跑可能发生端口冲突建议每个测试类指定一个独立端口不要全部用8089这种固定值CI 上爆端口冲突是常事。5.6 “SQL执行10秒自动关闭”这类配置会不会影响 RestClient 测试很多人会把 RestClient 和数据库连接池的超时概念搞混这里顺手捋一下。Spring Boot 中关于“SQL 执行 10 秒自动关闭”这类说法一般是指连接池的连接回收时长时间不归还或事务超时它影响的范围是数据库连接和 HTTP 客户端的超时完全是两条线。RestClient 的超时配置在ClientHttpRequestFactory层与 JDBC 无关。所以你在测试 RestClient 时不需要也没必要依赖 Spring 的spring.datasource.*超时配置连接超时和读超时都在 RestClient 自己的 RequestFactory 里设置。测试时为了不等待过久通常把 connectTimeout 设 1~3 秒、readTimeout 设 2~5 秒即可线上再根据接口实际响应时间放宽。5.7 测试时遇到JdkClientHttpRequestFactory和 HttpURLConnection 混用Spring Boot 3.2 默认的ClientHttpRequestFactory是JdkClientHttpRequestFactory但在某些 Spring Boot 小版本上如果 classpath 里有 Apache HttpClient 或 Jetty 的客户端自动配置可能选择其他工厂。这会导致你在本地跑测试时没问题一到 CI 就发现 RestClient 行为不一致比如代理设置、连接池参数全变了。建议在配置类里显式指定requestFactory不要依赖容器自动选择这样测试和生产环境行为才一致。如果你像我一样习惯在RestClientConfig里手动构建就把JdkClientHttpRequestFactory或你选择的工厂实现固定写死。6. 实操心得我踩过几次坑之后的几点体会写这篇文章之前我把团队里所有用到 RestClient 的模块翻了一遍发现很多测试代码还在用 RestTemplate 时代的MockRestServiceServer.createServer()老写法这让我挺感慨的。框架更新换代速度很快但很多人的测试知识还停留在上一代 API 上。我觉得 RestClient 的单元测试核心要把握住三点方案选型看需求——纯单元测试用 MockRestServiceServer需要验证 HTTP 细节用 WireMock响应类型要匹配——单个对象用ChefDto.class集合用ParameterizedTypeReference不要混用异常要全覆盖——正常返回、4xx、5xx、连接异常、读超时这五类场景都该有测试第三方接口才敢放心调。最后再分享一个小技巧MockRestServiceServer的expect支持一次声明多个请求的期望如果被测代码里循环调用了多次接口可以用expect(requestTo(...)).andExpect(...).andRespond(...)连续写多个最后统一verify()。但如果你对一个 URL 期望了两次且两次返回的内容不一样建议用andRespond的不同响应或MockRestResponseCreators的withSuccess参数变体分别处理不要让测试变成“最后一次赋值生效”那样排错会非常痛苦。对于刚接触 RestClient 的读者我建议先把 3.4 节的基础测试跑通再把 3.5 的异常场景补上最后再去研究 WireMock。测试是写给未来的自己看的一个能快速定位问题的测试类比什么架构理念都实在。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表