Mock 概念与常见方案:MockWebServer、MockInterceptor、回放测试

说到 Mock,很多刚接触 Android 测试的同学第一反应就是「造假数据」。嗯,其实没那么简单。Mock 的核心目的是让测试与外部依赖解耦,让我们的单元测试或集成测试不再依赖网络环境、服务器状态或者第三方 SDK 的可用性。说白了,就是让被测代码在可控的「虚拟环境」里跑。

我当年在一个支付项目里就吃过亏——测试环境后端经常挂掉,导致 CI 里一半的用例都因为网络超时而失败。后来我用 Mock 把网络层全部替换掉,构建时间从 40 分钟降到了 8 分钟。所以这一章,我们先把 Mock 的几种主流方案理清楚。

1. 什么是 Mock?

Mock 不是「伪造」,而是「替代」。在 Android 测试中,我们通常需要模拟网络请求的响应、数据库的返回值、甚至是硬件传感器的数据。对于 Retrofit 测试来说,最核心的就是模拟 HTTP 请求的返回。

一句话概括:Mock 就是让程序「以为」自己发了真的请求,但实际上我们在本地控制了返回的数据。

2. 三大常见方案

市面上能用来 Mock Retrofit 的工具不少,但最常用、最成熟的无非这三种:MockWebServer、MockInterceptor、以及我在这门课里要重点讲的回放测试。下面我一个一个拆开说。

2.1 MockWebServer

OKHttp 官方提供的测试工具。它会在本地启动一个真实的 HTTP 服务器,然后我们往里面塞预设的响应。测试时 Retrofit 的 baseUrl 指向这个本机地址(比如 http://localhost:8080)。

个人习惯:单个接口的异常情况测试(比如 500 或超时)我特别喜欢用 MockWebServer,因为不需要改任何生产代码,baseUrl 一换就行。

示例代码:

// 启动 MockWebServer
MockWebServer server = new MockWebServer();
server.start(8080);

// 预设响应
server.enqueue(new MockResponse()
    .setResponseCode(200)
    .setBody("{\"name\":\"test\",\"age\":18}")
);

// Retrofit 指向本地服务器
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(server.url("").toString())
    .build();

// 调用接口
ApiService api = retrofit.create(ApiService.class);
Response<User> response = api.getUser().execute();

// 断言
assertEquals("test", response.body().name);

// 关闭
server.shutdown();

优点很突出:完全模拟真实 HTTP 交互,请求头、响应头、状态码都可以控制。缺点嘛,每次测试都要启动和关闭服务器,速度上会慢一些。而且如果你有几百个测试用例,每个用例都要写一堆 enqueue,维护成本挺高。

2.2 MockInterceptor

这是更轻量级的方式。MockInterceptor 本质上是一个 OkHttp 的 Interceptor,在请求发出去之前就被拦截了,直接返回预设的数据。它不需要启动服务器,速度更快。

我刚开始用这个方案的时候觉得特别爽,直到有一次……嗯,后面避坑环节再说。

// 创建 MockInterceptor
MockInterceptor interceptor = new MockInterceptor();

// 按条件返回
interceptor.addRule()
    .urlContains("/user")
    .method("GET")
    .respondWith(new MockResponse()
        .setStatusCode(200)
        .setBody("{\"name\":\"test\"}")
    );

// 添加到 OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(interceptor)
    .build();

Retrofit retrofit = new Retrofit.Builder()
    .client(client)
    .baseUrl("https://api.example.com/")
    .build();
一个坑:MockInterceptor 无法验证真正的请求是否匹配,如果你把 URL 规则写错了,它可能直接返回默认的 404 或者 null。我曾经调试了一下午,结果发现规则里少打了一个斜杠。

2.3 回放测试(本章核心)

前面两种方案都是「预先写好返回什么」,而回放测试是「先录制真实请求,然后播放相同的数据」。流程如下:

  1. 录制阶段:把应用的真实请求和响应抓下来,存成本地文件(比如 YAML 或 JSON)。
  2. 回放阶段:测试时读取本地文件,匹配请求,直接返回录制的响应。

这有什么好处?最大的好处是数据真实。你不需要手动构造返回体,也不会漏掉某些字段。我维护的一个电商 App,API 返回的字段有五六十个,如果用手动 Mock 简直要疯。用回放测试,录一次就能覆盖所有字段。

但回放测试也有门槛:你需要一个稳定的「录制器」工具,以及一套匹配逻辑(比如按 URL、请求体、header 匹配)。后面几章我会带你一步步搭出来。

3. 方案对比

维度MockWebServerMockInterceptor回放测试
启动速度慢(启动服务器)快(拦截器)中等(文件 IO)
真实度极高(完整 HTTP)高(但无实际网络)极高(真实流量)
维护成本高(每个用例写 enqueue)中等(规则匹配)低(录制一次)
适合场景少量异常测试单元测试快速反馈集成测试、回归测试

4. 避坑指南

我曾经有一次用 MockInterceptor 测支付回调,因为请求体里有个动态 timestamp,导致所有回放匹配都失败。后来我被迫在 Mock 规则里加了正则表达式忽略时间戳字段——这件事告诉我,Mock 不是无脑直接套的。

还有一次,MockWebServer 在 CI 环境里因为端口被占用导致整个构建失败。我当时直接懵了。后来改了端口随机分配,才稳定下来。

总结:没有银弹。日常快速验证用 MockInterceptor,复杂场景或生产环境问题复现用 MockWebServer,要长期维护的回归测试,我强烈推荐回放测试。

5. 本章知识图谱

下面的 SVG 把三种方案的关系和适用情况画出来了,你可以把这张图当作本章的思维导图。

Mock 三种常用方案 Android App + Retrofit MockWebServer 本地启动 HTTP 服务器 真实网络栈,支持状态码 适合:单个接口异常测试 MockInterceptor OkHttp 拦截器 速度快,无需启动服务器 适合:单元测试快速反馈 回放测试 先录制真实流量 再匹配回放 适合:回归集成测试 推荐策略: 快速验证用 MockInterceptor → 复杂场景用 MockWebServer → 长期回归用回放测试

6. 我的一些补充

最后说点个人经验。很多人一上来就选 MockWebServer,觉得「官方出品,必属精品」。但说实话,对于大部分业务逻辑测试,MockInterceptor 完全够用,而且快得多。回放测试适合那种 API 字段特别多的场景,或者你需要验证「历史版本的行为」——比如修了一个 bug,怕影响之前的功能。

如果你觉得这三种方案还不够,别急,后面十几章我会带着你手写一个回放框架,你会发现回放测试的威力远远不止于「录制-播放」这么简单。

小练习:试着用 MockWebServer 模拟一个 503 错误(Service Unavailable),然后验证你的 App 能不能正确弹出「服务器繁忙」的提示。如果不会写,先看看上面的代码示例,改一个状态码就行。