4. 模拟网络响应:MockWebServer 启动、enqueue 响应、设置 HTTP 状态码与 Body
4.1 原理概述
在 Worker 对 Retrofit 网络调用进行单元测试时,真实 HTTP 服务会引入不确定性、环境依赖和速度瓶颈。MockWebServer 是 OkHttp 官方提供的轻量级内嵌 HTTP 服务,允许在本地回放预定义的响应,从而对 Retrofit 的请求/响应生命周期进行隔离验证。
- 启动:MockWebServer 在测试进程内绑定随机可用端口,无需外部服务。
- enqueue:将预构建的 MockResponse 对象放入响应队列,服务器按 FIFO 顺序逐条返回。
- 状态码与 Body:每个 MockResponse 可独立设置 HTTP 状态码、Header 和响应体,覆盖成功、失败、重定向、超时等场景。
4.2 实现流程
- 在测试依赖中引入 MockWebServer(
com.squareup.okhttp3:mockwebserver)。 - 在
@Before中实例化 MockWebServer 并调用start()。 - 构造
MockResponse对象,设置状态码、Header 和 Body。 - 调用
server.enqueue(mockResponse)将响应加入队列。 - 用
server.url("/api/endpoint")获取完整 base URL 构建 Retrofit 实例。 - 执行 Worker 的网络操作,断言实际结果与预期一致。
- 在
@After中调用server.shutdown()。
💡 提示:MockWebServer 默认每次调用只消耗一个 enqueue 的响应;若队列为空则阻塞等待下一个响应。可在测试中连续 enqueue 多个响应以模拟分页或重试。
4.3 核心代码示例
以下 Kotlin 测试展示了完整的 MockWebServer 设置与响应模拟:
class WorkerNetworkTest {
private lateinit var mockWebServer: MockWebServer
private lateinit var apiService: ApiService
@Before
fun setUp() {
mockWebServer = MockWebServer()
mockWebServer.start()
val retrofit = Retrofit.Builder()
.baseUrl(mockWebServer.url("/")) // 使用 MockWebServer 的 base URL
.addConverterFactory(GsonConverterFactory.create())
.build()
apiService = retrofit.create(ApiService::class.java)
}
@Test
fun `test successful response with body`() {
// 构造 MockResponse
val successResponse = MockResponse()
.setResponseCode(200)
.setHeader("Content-Type", "application/json; charset=utf-8")
.setBody("""{"userId":1,"title":"test","completed":false}""")
mockWebServer.enqueue(successResponse)
val worker = MyNetworkWorker(apiService)
val result = worker.doWork()
assertTrue(result.isSuccess)
assertEquals("test", result.getOrNull()?.title)
}
@Test
fun `test 404 error handling`() {
val errorResponse = MockResponse()
.setResponseCode(404)
.setBody("Not Found")
mockWebServer.enqueue(errorResponse)
val worker = MyNetworkWorker(apiService)
val result = worker.doWork()
assertTrue(result.isFailure)
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
}
4.4 API / 配置详解
| 方法 | 说明 | 典型值 |
|---|---|---|
MockWebServer.start() |
启动服务器,绑定随机端口 | — |
MockWebServer.enqueue(MockResponse) |
将响应加入 FIFO 队列 | — |
MockResponse.setResponseCode(int) |
设置 HTTP 状态码 | 200, 400, 404, 500, 503 |
MockResponse.setHeader(String, String) |
添加响应头 | Content-Type: application/json |
MockResponse.setBody(String) |
设置响应体(字符串形式) | JSON、XML 或纯文本 |
MockWebServer.url(String) |
返回完整 URL,包含主机和端口 | http://127.0.0.1:PORT/api |
MockWebServer.shutdown() |
优雅关闭服务器,释放端口 | — |
4.5 调试技巧
- 使用
server.takeRequest()获取实际收到的请求对象,调试 Headers、Path 和 Body。 - 利用
server.requestCount验证请求次数是否符合预期。 - 使用
mockWebServer.setDispatcher(Dispatcher)自定义响应逻辑,实现条件分支。
// 调试示例:打印服务器收到的请求
val request = mockWebServer.takeRequest()
println("Path: ${request.path}")
println("Headers: ${request.headers}")
println("Body: ${request.body.readUtf8()}")
⚠ 常见错误
- 忘记
start()导致BindException。 - 多次调用
start()后未shutdown()引发端口泄露。 - enqueue 响应数量少于实际请求次数,导致测试线程被永久阻塞。
- Base URL 中使用
/api但未以斜杠结尾,导致 Retrofit 拼接路径异常。
4.6 性能与稳定性优化
- 尽量在
@BeforeClass中单次启动 MockWebServer,使用beforeAll减少反复启动开销。 - 为每个测试类使用唯一实例,避免端口冲突(随机端口自动处理)。
- 对于极低延迟场景,可设置
mockWebServer.setBodyDelay(0)消除模拟延迟。
📌 知识结构总结
MockWebServer 启动 → 构造 MockResponse(状态码 + Body + Headers)→ enqueue 进队列 → Worker 通过 Retrofit 发出请求 → 服务器按顺序返回 → 断言结果 → 关闭服务器。
MockWebServer 启动 → 构造 MockResponse(状态码 + Body + Headers)→ enqueue 进队列 → Worker 通过 Retrofit 发出请求 → 服务器按顺序返回 → 断言结果 → 关闭服务器。
4.7 常见问题与排查
- 响应队列空导致测试挂起:检查 enqueue 次数是否足够,或使用
server.setDispatcher兜底。 - 端口已被占用:确保每个测试类只启一个实例,且
shutdown在 finally 块执行。 - 请求路径不匹配:用
server.takeRequest().path比对实际路径。 - Body 编码问题:使用
setBody(ByteString)或显式设置 charset。
✅ 最佳实践
为每个测试方法(或每组相关方法)enqueue 独立响应集合,并在断言前使用
为每个测试方法(或每组相关方法)enqueue 独立响应集合,并在断言前使用
await().untilAsserted 确保异步 Worker 完成。搭配 IdlingResource 可进一步稳定测试。