Mock测试工具概览:Mockito、MockWebServer、WireMock

说实话,刚开始做接口Mock的时候,我也纠结过:到底用哪个工具?

三个工具各有各的脾气。Mockito、MockWebServer、WireMock,名字听起来挺像,但用起来完全不是一回事。我最初入坑时,在同一个项目里来回切换,闹了不少笑话。

这一节,我就把自己摸索出来的心得掰开揉碎讲给你。

核心认知:三种工具服务于不同的Mock场景。选错工具,代码会很难维护。

三者的核心定位

先简单说清楚它们各自是干什么的:

工具 定位 作用层次 运行方式 是否支持HTTP接口Mock
Mockito Java单元测试Mock框架 代码层面(对象、方法) 内嵌在JVM中 不支持(需配合其他工具)
MockWebServer 轻量级HTTP服务器Mock HTTP请求/响应层 启动本地Socket服务 支持(专为OkHttp/Retrofit设计)
WireMock HTTP API Mock & 录制框架 HTTP请求/响应层 独立进程或内嵌启动 支持(功能强大且完善)

你看这个表,就清楚多了。Mockito管的是代码里的对象行为,MockWebServer和WireMock管的是网络请求。很多人一上来就用Mockito去Mock网络请求,结果发现根本拦截不了——我最初也犯过这个错。

Mock工具分层关系图 单元测试层 Mockito 隔离依赖对象 MockK Kotlin首选Mock框架 EasyMock 传统方案 接口测试层 MockWebServer 轻量、快速、OkHttp亲儿子 WireMock 功能完善、支持录制回放 生产回放层 GoReplay / VCR 流量录制与回放

选择口诀:单测Mock用Mockito,接口Mock看场景——轻量测试选MockWebServer,功能需求上WireMock。

Mockito:单元测试的瑞士军刀

Mockito是我用得最多的Mock框架。它解决的是对象依赖问题。你想想看,一个Service里调了三个Repository,单元测试时总不能真的去连数据库吧?这时候Mockito就派上用场了。

我第一次用Mockito是在一个小型支付项目里。当时要测试订单状态流转,但无法实际调用支付网关。Mockito把网关客户端Mock掉后,我可以模拟各种回调场景——支付成功、支付失败、超时重试,一口气全测了。

代码看起来是这样:

// Mockito 核心用法
@Mock
private PaymentGateway paymentGateway;

@InjectMocks
private OrderService orderService;

@Test
void testPaymentSuccess() {
    // 模拟:调用网关返回成功
    when(paymentGateway.charge(any())).thenReturn(new PaymentResult(true, "SUCCESS"));
    
    Order order = orderService.processPayment(new Order("123", 99.9));
    assertEquals(OrderStatus.PAID, order.getStatus());
}

嗯,这里要注意:Mockito只适合Mock代码内部的对象依赖。它管不了网络请求,因为网络请求是跨进程的。很多新手在这上面栽跟头。

MockWebServer:专治接口联调的利器

再来说说MockWebServer。这是Square公司(也就是OkHttp亲爹)推出的测试工具。它与OkHttp和Retrofit配合得天衣无缝。说白了,它在本地起一个真正的HTTP服务器,然后让你的Retrofit请求打到这个服务器上。

我记得有一次项目,服务端接口还没写好,但前端已经催着联调了。我把MockWebServer一开,定义好几个接口的Mock响应,前后端同时在Mock数据上跑通流程,效率提升不是一星半点。

这是它的基本用法:

// MockWebServer 核心用法
MockWebServer mockServer = new MockWebServer();
mockServer.start();

// 配置Retrofit指向Mock服务器
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(mockServer.url("/"))
    .build();

// 模拟接口响应(超时、错误码都能模拟)
mockServer.enqueue(new MockResponse()
    .setResponseCode(200)
    .setBody("{\"status\":\"ok\"}")
    .setBodyDelay(500, TimeUnit.MILLISECONDS));

// 还可以模拟异常场景
mockServer.enqueue(new MockResponse()
    .setResponseCode(500)
    .setBody("{\"error\":\"internal error\"}"));

// 最后要记得关闭
mockServer.shutdown();

避坑指南:我之前遇到过一个问题——MockWebServer默认端口是动态分配的,如果硬编码端口号会导致测试在不同机器上跑不过。务必用mockServer.url("/")动态获取baseUrl。

MockWebServer的优势在哪?轻量、快、开箱即用。它不需要额外配置,不需要准备文件,几行代码就完事了。

但它的缺点也很明显:不支持录制回放,Mock数据都在代码里写死,维护起来比较费劲。

WireMock:契约测试的瑞士军刀

最后聊聊WireMock。我个人觉得,它是三者中最「重型」的一个,但也最「强大」。

WireMock像是一个完整的Mock服务器框架。它支持:

  • Stub配置:通过JSON或代码定义Mock响应
  • 请求匹配:支持URL、Header、Body、Query参数精准匹配
  • 录制回放:这是它最大的卖点——先录制真实接口,再回放做比对
  • 独立运行:可以启动一个WireMock进程,供团队共用

我曾在项目中用WireMock模拟第三方支付接口。对方接口不稳定,时不时飘个超时,每次测试都要等。我把他们的真实响应录制下来,然后在本地回放,测试稳定性从60%直接提升到99%。

配置示例:

// WireMock 核心用法
// 1. 启动WireMock服务器
WireMockServer wireMockServer = new WireMockServer(8080);
wireMockServer.start();

// 2. 配置Stub(支持JSON配置,也可以写在代码里)
stubFor(get(urlEqualTo("/api/user/123"))
    .willReturn(aResponse()
        .withStatus(200)
        .withHeader("Content-Type", "application/json")
        .withBody("{\"id\":123,\"name\":\"Alice\"}")
    ));

// 3. 录制模式(记一次就够了)
// 启动时带 --record-mappings 参数
// 访问真实接口,自动生成mapping文件

经验之谈:WireMock的录制功能特别适合契约测试。我和服务端团队约定好接口契约,把契约文件直接转成WireMock的mapping,服务端重构时,我的测试一秒都不用改。

怎么选?我给的建议

聊到这里,你应该明白了——这三个工具不是替代关系,而是互补关系。在实际项目中,我通常是混合使用的:

测试场景 推荐工具 理由
单元测试(Service/Repository层) Mockito 快速隔离外部依赖
Retrofit接口联调与基础测试 MockWebServer 轻量、与OkHttp深度集成
复杂场景模拟(超时、错误码组合) MockWebServer 精确控制每次响应
契约测试 / 接口录制回放 WireMock 录制功能强大、可脱离代码
集成测试(需Mock完整API服务) WireMock 支持独立进程部署

小建议:如果你和我一样懒,建议优先掌握MockWebServer。因为它的学习成本最低,一个类搞掂所有。等遇到需要录制回放的场景时,再补上WireMock也不迟。

为什么要这样安排?你想想看,不同阶段用不同的工具——单元测试、接口测试、契约测试,它们根本不是一个层面的事。非要在一个工具上吊死,反而会把事情搞复杂。

举个例子,我见过有人用Mockito里面的when()去模拟OkHttp的Call对象,绕了一大圈,代码又脏又难维护。其实换成MockWebServer,两行代码就解决问题了。

所以,别纠结谁更好。把这些工具当工具,该用哪个用哪个。

特别注意:如果我需要在同一个测试类里同时使用Mockito和MockWebServer,千万要注意初始化顺序。MockWebServer必须先启动,否则Retrofit实例化时会找不到baseUrl。这个顺序问题,我曾经排查了一下午才找到原因。

嗯,工具概览就讲到这里。记住核心一点:不同层次用不同工具,不要张冠李戴。接下来我们会深入到MockWebServer的具体用法中去,直接上手写代码。