网络请求的单元测试:为什么需要Mock,传统测试的痛点
大家有没有碰到过这种情况——写完一个网络请求的单元测试,跑一遍,绿了。结果第二天一上班,测试全红。你查了半天,发现是后端某个接口挂了。你写的测试,居然因为服务端挂了而失败。
这其实是个经典问题。我以前也这么想:测试嘛,能真实请求接口才是最靠谱的。但后来被坑了几次,才慢慢明白——单元测试不该依赖外部环境。
这一章,我们就聊聊为什么网络请求测试里,Mock是必需品,以及传统测试方式到底痛在哪里。
传统测试方式:直接请求真实接口
先看一个典型的测试代码。假设我们有个ApiService,用来获取用户信息:
public class UserServiceTest {
private ApiService apiService;
@Before
public void setup() {
// 直接创建一个真实客户端
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
apiService = retrofit.create(ApiService.class);
}
@Test
public void testGetUser() throws Exception {
User user = apiService.getUser(1).execute().body();
Assert.assertNotNull(user);
Assert.assertEquals("张三", user.getName());
}
}
这段代码看着没问题对吧?但实际上,它的问题特别多。我总结了四个核心痛点:
| 痛点 | 具体表现 | 影响 |
|---|---|---|
| 依赖外部环境 | 测试能否通过,完全取决于API是否可用 | 网络波动、服务重启、版本变更都可能导致测试失败 |
| 测试速度慢 | 每次测试要发起真实网络请求 | 几十个接口跑一轮要几分钟,CI流水线被拖慢 |
| 难以覆盖边界场景 | 你无法控制服务端返回超时、500错误、空数据 | 异常处理和边界逻辑几乎无法测试 |
| 测试不稳定(Flaky) | 同样的代码,不同时间跑,结果不一样 | 团队逐渐失去对测试的信任,甚至不再关注红绿 |
为什么测试会“依赖外部环境”
说白了,单元测试的核心原则是:只测我这一个类,不测别人的代码。你的UserService内部调了Retrofit,Retrofit又调了OkHttp,OkHttp再发网络请求。这一条链路下来,你根本没法控制中间哪个环节出问题。
我在一个电商项目里遇到过这种事。有一天所有测试突然红了,排查了两小时才发现是DNS解析出问题了——我们根本没改代码。你想想看,这种事多消耗团队精力。
核心原则:单元测试应该像实验室实验,所有条件可控制、可重复。Mock就是你的“实验环境搭建工具”。
Mock到底是什么
Mock,说白了就是造个假的服务替身。它不真的发网络请求,而是根据你预设的规则返回数据。这样你的测试就变成了:
- 可重复——每次跑结果一样
- 速度快——毫秒级返回,不用等网络
- 可控——你想返回什么就返回什么,包括异常
举个例子,用Mockito写个简单的Mock测试:
@Test
public void testGetUserWithMock() {
// 创建Mock对象
ApiService mockService = mock(ApiService.class);
// 预设返回数据
User mockUser = new User(1, "张三");
when(mockService.getUser(1)).thenReturn(Response.success(mockUser));
// 执行测试逻辑
User user = mockService.getUser(1).body();
Assert.assertEquals("张三", user.getName());
}
你看,这段测试里没有任何网络请求,但业务逻辑该测的都测到了。而且它永远稳定——只要你不改代码,跑一百遍也是一样的结果。
Mock的几种常见实现方式
在Android领域,主流Mock方案有三类:
| 方案 | 原理 | 适用场景 | 缺点 |
|---|---|---|---|
| Mockito/PowerMock | 字节码代理,动态生成子类 | Mock单个类或接口 | 对final/static方法支持不够好 |
| MockWebServer (OkHttp) | 启动本地Socket服务器返回预定义响应 | 模拟HTTP层的各种场景 | 需要启动轻量服务,略重 |
| 自定义Interceptor | 在OkHttp拦截器层拦截请求并伪造响应 | 需要精细控制请求/响应流程 | 代码侵入性稍高 |
我个人习惯是:单元测试用Mockito,集成测试用MockWebServer。这个后面几章会展开讲。
传统测试还有什么坑
除了上面说的四个痛点,还有一个很隐蔽的问题——数据污染。我曾经在一个测试环境里跑自动化,测试往数据库写了一批脏数据,结果其他开发人员查问题时,被这些假数据误导了整整一下午。
另外,如果你依赖真实接口,你就没法做“先写测试、再写实现”的TDD流程。因为服务端还没写好,你怎么测?这直接限制了开发节奏。
注意:Mock不是万能的。它只能验证你代码的行为是否正确,不能验证真实接口的契约是否一致。因此我们还会需要集成测试和契约测试。这个后面也会讲。
这个章节的核心逻辑
总结一下网络测试的核心问题与Mock的位置,下面这张图能帮你看清整体脉络:
我的建议:刚开始接触Mock的同学,可以先从Mockito入手。它门槛最低,文档丰富,覆盖大部分场景。等用熟了,再根据项目需求切换到MockWebServer或自定义方案。
这一章的收获
- 理解了为什么不能用真实API做单元测试——依赖、速度、覆盖率、稳定性四个维度都不行
- 明白了Mock的本质:创建可控的测试环境,让测试只关注你的代码逻辑
- 知道了三种常见的Mock实现方式,以及各自的适用场景
- 看到了传统测试的潜在风险——数据污染、无法TDD、团队信任流失