MockWebServer 配置:模拟 HTTP 服务

这一章我们来聊 MockWebServer,说实话它是做 Android 或 Java 网络层测试时我几乎天天用的工具。你想想看,如果每次跑测试都要连真实服务器,那速度慢不说,还得处理网络抖动、数据变化,简直自找麻烦。MockWebServer 就是帮你本地起一个假的 HTTP 服务器,你让它返回什么它就返回什么,测试完全可控。

为什么需要 MockWebServer

我刚开始写网络测试时,直接用 Retrofit 真调用。结果有一次后端接口突然挂了,我这边全部测试红成一片,排查半天才发现不是我的 bug。从那以后我学乖了——测试里用的 HTTP 服务,必须能自己模拟。

MockWebServer 的核心价值就是:

  • 隔离外部依赖——测试只测你写的逻辑,不测网络
  • 快速且稳定——本地启动,毫秒级响应
  • 精准控制返回值——你想模拟 200、404、超时,一句话搞定

一句话总结:MockWebServer 让你在测试环境里扮演“假后端”,想给什么数据就给什么数据。

在 Gradle 中添加依赖

我建议你直接用 Square 官方维护的 mockwebserver 库。它和 OkHttp 出自同一团队,配合最好。注意要用 testImplementation,千万别打成 implementation,否则生产包里会带上它。

dependencies {
    // MockWebServer 核心库
    testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")

    // 如果还需要对 OkHttp Client 做断言,可以一并加
    testImplementation("com.squareup.okhttp3:okhttp:4.12.0")

    // 常用的测试框架
    testImplementation("junit:junit:4.13.2")
}

版本号建议统一用 OkHttp 的版本,防止出现兼容问题。我也见过有人用 4.10.0 结果和 OkHttp 4.12 对不上,排查了半小时。嗯,最好保持完全一样。

基本用法示例

咱们直接上手。做个最简单的单元测试:启动一个 MockWebServer,让它对任意请求返回一段 JSON。

import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Before
import org.junit.Test

class ApiServiceTest {
    private lateinit var server: MockWebServer
    private lateinit var client: OkHttpClient

    @Before
    fun setUp() {
        server = MockWebServer()
        server.start()  // 默认随机端口
        client = OkHttpClient.Builder()
            .baseUrl(server.url("").toString())  // 把客户端指向 Mock 地址
            .build()
    }

    @After
    fun tearDown() {
        server.shutdown()
    }

    @Test
    fun `test success response`() {
        // 1. 准备模拟响应
        val mockResponse = MockResponse()
            .setResponseCode(200)
            .setBody("{\"name\":\"MockWebServer\",\"version\":\"4.12\"}")
            .addHeader("Content-Type", "application/json")
        server.enqueue(mockResponse)

        // 2. 发起真实 HTTP 请求(client 已指向 mock 地址)
        val request = Request.Builder()
            .url("${server.url("")}api/info")
            .build()
        val response = client.newCall(request).execute()

        // 3. 断言
        assertThat(response.code).isEqualTo(200)
        val body = response.body?.string()
        assertThat(body).contains("\"name\":\"MockWebServer\"")

        // 4. (可选)验证 mock 收到的请求
        val recordedRequest = server.takeRequest()
        assertThat(recordedRequest.path).isEqualTo("/api/info")
        assertThat(recordedRequest.method).isEqualTo("GET")
    }
}

你看,代码很清晰。启动 server,配置一个 MockResponse 然后 enqueue,之后你的客户端发请求就能收到这个模拟数据。记得测试结束一定要 shutdown,否则端口一直占着。

我的习惯:在 @Before 里给一个 server.start(),@After 里 server.shutdown()。如果多个测试类共享同一个 server,要小心端口复用。我个人更喜欢每个测试类都创建自己的 server,更干净。

进阶玩法:模拟延迟、body 为空、错误码

MockWebServer 还能模拟各种网络故障。举个例子:

// 模拟 500 错误
server.enqueue(MockResponse().setResponseCode(500).setBody("Internal Server Error"))

// 模拟超时(设置延迟 5 秒)
server.enqueue(MockResponse().setBodyDelay(5, TimeUnit.SECONDS).setResponseCode(200))

// 模拟 body 为空
server.enqueue(MockResponse().setResponseCode(200).setBody(""))

我曾经踩过一个坑:测试用例里只设置了 setBody(""),但忘记设置 Content-Type,结果解析 JSON 时报 NullPointerException。嗯,记得把 header 也补全。

流程图:MockWebServer 工作原理

下面这张图帮你梳理整个调用流程,看着更直观。

MockWebServer 模拟 HTTP 服务流程 测试代码 MockWebServer MockResponse 队列 客户端 (OkHttp) 断言验证 takeRequest() 配置阶段 响应队列 执行阶段 验证阶段 请求检查 关键点 每个 enqueue 的 MockResponse 按先进先出顺序使用;takeRequest() 可以取出服务器收到的请求用于断言。 测试结束时调用 server.shutdown() 释放端口,避免下次运行冲突。

常用 API 速查表

我整理了一个小表,方便你随手翻。

方法 说明
MockWebServer.start()启动服务器(默认随机端口)
server.enqueue(MockResponse)将响应放入队列,按顺序返回
server.url("")获取服务器基准 URL(含端口)
server.shutdown()关闭服务器,释放端口
server.takeRequest()取出最新的请求记录,可验证 path/method/headers
MockResponse.setResponseCode(int)设置状态码,如 200、404、500
MockResponse.setBody(String)设置响应体
MockResponse.setBodyDelay(...)设置响应延迟,用于模拟超时场景
MockResponse.addHeader(String, String)添加响应头

警告:如果你用 server.start(8080) 指定固定端口,多个测试并行或 CI 同时跑时可能端口冲突。我建议用默认随机端口,省心省力。

避坑指南

我遇到过几个常见问题,列出来帮你绕过去:

  1. MockResponse 不设置 body 直接返回 null:记得至少 setBody(""),否则框架抛空指针。
  2. enqueue 顺序和请求顺序不一致:多线程测试里要注意,默认情况下 MockWebServer 是单线程处理,但如果你并发发请求,队列顺序可能被打乱。我建议用 server.enqueue() 后等到请求完成再 enqueue 下一个。
  3. takeRequest() 会阻塞直到有请求:如果你的测试里没有发请求,它会一直等,容易导致超时。最好先保证有请求再调用。