4. 常见Mock工具介绍(Mockito, WireMock等)
说实话,Mock 工具满大街都是,但真正在 CI 流水线上用得顺手的,我翻来覆去就那么几把。今天咱们就聊聊我这些年用过的 Mockito、WireMock 以及几个不得不提的兄弟。你想想看,选错工具就像拿螺丝刀拧水管——不是不能用,但浑身难受。
4.1 Mockito:Java 单元测试的瑞士军刀
Mockito 是我个人用得最多的 Java Mock 框架。它本质上是动态代理,通过 CGLIB 或 ByteBuddy 在运行时生成接口或类的代理对象。
核心能力只有三件事:
- Stub(打桩):指定某个方法返回什么值
- Verify(验证):检查方法有没有被调用、调了几次、传了什么参数
- Spy(部分Mock):保留真实对象的部分行为
我踩过的坑: 很多人以为 Mockito 能 mock 静态方法,原生版本其实不行。直到 Mockito 3.4 引入了 mockStatic(),但需要额外依赖 mockito-inline。我曾经在旧项目里强行用 PowerMock 配合,结果每次升级依赖都像扒层皮。
一个典型的 Mockito 测试长这样:
@Test
public void testOrderService() {
// 1. 创建Mock
PaymentGateway gateway = mock(PaymentGateway.class);
OrderService service = new OrderService(gateway);
// 2. 打桩:当支付调用时返回成功
when(gateway.charge(anyDouble())).thenReturn(true);
// 3. 执行被测逻辑
boolean result = service.placeOrder(99.9);
// 4. 验证交互
verify(gateway).charge(99.9);
assertTrue(result);
}
嗯,这里要注意:when().thenReturn() 和 doReturn().when() 有什么区别?很简单——when 写法会在打桩时真正调用一次真实方法(对 spy 对象有副作用),而 doReturn 不会。我习惯所有 spy 场景都用 doReturn,避免意外。
4.2 WireMock:让 HTTP 调用变听话
如果 Mockito 是单元测试的利器,那 WireMock 就是集成测试和契约测试的标配。它其实是一个可编程的 HTTP 服务器,运行在本地端口上,模拟真实 API 的行为。
我一般在 CI 里这样用:
- 测试启动前,内嵌 WireMock 服务(Spring Boot 用
@AutoConfigureWireMock) - 注册 Stub:匹配请求路径、Header、Body,返回指定状态码和响应
- 测试结束自动关闭
个人习惯: WireMock 的 __files 目录下放静态响应 JSON,再用 Stub 指向文件名。这样改响应内容不用改测试代码,CI 跑失败了排查也方便。
一个最简的 Stub 示例:
// 内嵌启动
WireMockServer wireMockServer = new WireMockServer(8089);
// 注册Stub
stubFor(get(urlEqualTo("/api/users/1"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("{\"id\":1, \"name\":\"Alice\"}")));
// 使用RestTemplate调用 http://localhost:8089/api/users/1
// 返回的就是我们伪造的数据
WireMock 还有个 Kill 功能——记录回放(Record & Playback)。你可以先代理真实 API 跑一次测试,把请求和响应录制成 mapping 文件;后面 CI 运行直接用录好的数据,不再依赖真实服务。这个模式叫“Mock 回放”,我后续章节会重点讲。
4.3 其他工具不能不提
除了上面两位,还有几个工具我偶尔换着用:
| 工具 | 定位 | 我眼中的亮点 | 需要注意 |
|---|---|---|---|
| EasyMock | Java 单元测试 | 严格匹配调用顺序,适合顺序敏感场景 | API 比 Mockito 啰嗦,社区已冷 |
| MockServer | HTTP/HTTPS 模拟 | 支持 TLS、Forward、Expectation 验证 | 配置复杂,轻量级场景不如 WireMock |
| Mountebank | 跨语言 Mock 服务 | 支持 TCP、SMTP 等非 HTTP 协议 | 用 JavaScript 配置,学习曲线略陡 |
| gRPC Mock(如 gomock) | 微服务 gRPC 测试 | Protobuf 契约驱动,代码生成完胜手写 | 依赖代码生成,团队需统一流程 |
我曾经犯的错: 有一次在 CI 里同时用 Mockito 和 WireMock,结果单元测试用 Mockito 打牙祭,集成测试用 WireMock 模拟外部 API,但测试数据不一致(Mockito 里用的假数据跟 WireMock 的响应对不上)。后来我强制统一用一个“测试数据工厂”,两端的数据都从同一个 JSON 文件读取,才彻底解决。
4.4 怎么选?我的三板斧
选 Mock 工具说白了就三问:
- Mock 什么对象? Java POJO / 接口 → Mockito;HTTP 服务 → WireMock / MockServer;不同语言服务 → Mountebank
- 在什么阶段用? 单元测试 → Mockito 足够;集成测试、CI 中的外部依赖 → WireMock 首选
- 要不要反向验证? 只需要打桩 → 哪个顺手用哪个;需要验证请求头、Body 等细节 → WireMock 的
verify()不可替代
下面这张图是我自己整理的 Mock 工具选择路径,你在 CI 里按这个思路走基本不会偏:
4.5 实操建议:CI 里的 Mock 选用清单
如果你现在正搭 CI 流水线,我建议你先把下面这份清单过一遍:
- Java 后端项目:单元测试用 Mockito(依赖
mockito-core),集成测试用 WireMock(wiremock-standalone单 jar 方便 CI 缓存) - 微服务间调用:每个服务启动时用 WireMock 模拟下游,配合 Spring Cloud Contract 或 Pact 做契约测试
- 有第三方支付/短信等:优先用 WireMock 的录制回放(Record & Playback),而不是自己编造响应——真实业务场景里的各种边界状态你未必想得到
- 单元测试用不到 WireMock:别为了炫技把 WireMock 扔进所有测试,启动 HTTP 服务会拉慢几十毫秒,对单元测试来说没必要
我的一个小秘密: 写 Mock 测试时,我总会在 @Before 或 @BeforeEach 里重置所有 Mock(Mockito 的 Mockito.reset() 或 WireMock 的 resetAll())。这样每条测试用例之间互不干扰,CI 上随机顺序跑也不会翻车。
好了,工具就聊到这。记住,Mock 工具只是手段,目标是让 CI 跑得快、跑得稳、跑得可重复。下一条,咱们直接进实战:怎么在 CI 里搭一套 WireMock 的录制回放流程。