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 工具说白了就三问:

  1. Mock 什么对象? Java POJO / 接口 → Mockito;HTTP 服务 → WireMock / MockServer;不同语言服务 → Mountebank
  2. 在什么阶段用? 单元测试 → Mockito 足够;集成测试、CI 中的外部依赖 → WireMock 首选
  3. 要不要反向验证? 只需要打桩 → 哪个顺手用哪个;需要验证请求头、Body 等细节 → WireMock 的 verify() 不可替代

下面这张图是我自己整理的 Mock 工具选择路径,你在 CI 里按这个思路走基本不会偏:

Mock 工具选型框架图 CI 中的 Mock 工具选型框架 需要 Mock 什么? Java 对象 / 接口 HTTP 服务 / API 其他协议 Mockito EasyMock WireMock MockServer Mountebank 单元测试 / 方法级 Mock 集成测试 / CI 中替代外部依赖 系统测试 / 跨协议 核心原则:单元测试优先 Mockito,外部依赖选 WireMock 复杂场景可组合使用,但注意数据一致性 💡 说明:选择工具时,先核心问题“Mock 什么对象”,再向下锁定具体工具。同一项目中可混用,但建议统一入口管理 Stub 数据。

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 的录制回放流程。