网络层测试:MockWebServer搭建、测试API接口响应、测试网络错误处理、测试JSON解析、测试拦截器与日志

好,咱们今天聊网络层测试。说实话,这是Android单元测试里最容易被忽视、但又最值得投入的一环。为什么?因为你的App几乎不可能脱离网络存在。登录、刷数据、上传图片……哪一步离得开API?但网络请求偏偏又是最不可控的——服务器可能挂、网络可能断、返回的JSON可能格式不对。

那怎么办?我的答案很简单:用MockWebServer。它能在本地模拟一个HTTP服务器,让你的测试代码像跟真实服务器通信一样,但完全可控。说白了,就是「假戏真做」。

核心思路:用MockWebServer替换真实服务器,让测试跑在本地,快、稳、可重复。

1. MockWebServer 搭建

先加依赖。在 build.gradle 里加上:

testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
androidTestImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")

然后写一个测试基类。我个人习惯把MockWebServer的启动和关闭放在 @Before@After 里:

class ApiServiceTest {
    private lateinit var mockWebServer: MockWebServer
    private lateinit var apiService: ApiService

    @Before
    fun setup() {
        mockWebServer = MockWebServer()
        mockWebServer.start()

        val baseUrl = mockWebServer.url("/").toString()
        apiService = Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }

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

嗯,这里要注意:mockWebServer.url("/") 返回的是 HttpUrl 对象,转成字符串后就是 http://127.0.0.1:随机端口/。每次启动端口都不同,所以测试之间不会冲突。

2. 测试API接口响应

MockWebServer最爽的一点是:你可以精确控制返回什么。比如模拟一个成功的用户列表:

@Test
fun testGetUsers_success() {
    // 准备mock数据
    val mockResponse = MockResponse()
        .setResponseCode(200)
        .setBody("""
            [
                {"id": 1, "name": "Alice"},
                {"id": 2, "name": "Bob"}
            ]
        """.trimIndent())
        .setHeader("Content-Type", "application/json")

    mockWebServer.enqueue(mockResponse)

    // 执行请求
    val users = apiService.getUsers().execute().body()

    // 验证结果
    assertThat(users).hasSize(2)
    assertThat(users?.get(0)?.name).isEqualTo("Alice")
}

我在项目中遇到过一个问题:有时候mock数据写对了,但测试就是不过。后来发现是 setHeader("Content-Type", "application/json") 没加,Retrofit解析器直接报错。所以这个头信息千万别漏。

3. 测试网络错误处理

网络错误分两种:HTTP错误(4xx、5xx)和网络异常(超时、断开)。MockWebServer都能模拟。

HTTP错误:

@Test
fun testGetUsers_unauthorized() {
    val mockResponse = MockResponse()
        .setResponseCode(401)
        .setBody("{\"error\": \"Unauthorized\"}")

    mockWebServer.enqueue(mockResponse)

    val response = apiService.getUsers().execute()

    assertThat(response.isSuccessful).isFalse()
    assertThat(response.code()).isEqualTo(401)
}

网络异常:

@Test
fun testGetUsers_timeout() {
    // 模拟超时:延迟10秒,但测试只等2秒
    val mockResponse = MockResponse()
        .setBodyDelay(10, TimeUnit.SECONDS)

    mockWebServer.enqueue(mockResponse)

    try {
        apiService.getUsers().execute()
        fail("应该抛出SocketTimeoutException")
    } catch (e: SocketTimeoutException) {
        // 预期异常
    }
}

我曾经踩过一个坑:setBodyDelay 的单位是毫秒还是秒?答案是毫秒。但为了可读性,我习惯用 TimeUnit.SECONDS 转一下。你想想看,如果直接写 10000,别人看代码还得算一下,多累。

4. 测试JSON解析

JSON解析的测试,说白了就是验证「服务器返回的字段名」和「Kotlin数据类的字段名」是否匹配。我见过太多因为后端改了字段名、前端没同步导致的崩溃。

@Test
fun testJsonParsing_unknownField() {
    // 模拟返回一个未知字段
    val mockResponse = MockResponse()
        .setResponseCode(200)
        .setBody("""
            {"id": 1, "name": "Alice", "extra_field": "ignored"}
        """.trimIndent())

    mockWebServer.enqueue(mockResponse)

    val user = apiService.getUser(1).execute().body()

    // 未知字段应该被忽略,不会崩溃
    assertThat(user?.id).isEqualTo(1)
    assertThat(user?.name).isEqualTo("Alice")
}

小技巧:如果数据类用了 @SerializedName 注解,记得测试一下字段名映射是否正确。比如后端返回 user_name,你映射成 userName,写个测试验证一下,能省很多排查时间。

5. 测试拦截器与日志

拦截器是OkHttp的灵魂。但怎么测试它?MockWebServer配合自定义拦截器,可以做到。

假设你有一个日志拦截器,每次请求都打印URL和耗时:

class LoggingInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val startTime = System.nanoTime()
        val response = chain.proceed(request)
        val duration = (System.nanoTime() - startTime) / 1_000_000
        Log.d("HTTP", "${request.url} 耗时 ${duration}ms")
        return response
    }
}

测试这个拦截器,可以用MockWebServer捕获请求:

@Test
fun testLoggingInterceptor() {
    val mockResponse = MockResponse()
        .setResponseCode(200)
        .setBody("OK")

    mockWebServer.enqueue(mockResponse)

    // 构建带拦截器的OkHttpClient
    val client = OkHttpClient.Builder()
        .addInterceptor(LoggingInterceptor())
        .build()

    val request = Request.Builder()
        .url(mockWebServer.url("/test"))
        .build()

    val response = client.newCall(request).execute()

    // 验证请求被正确发送
    val recordedRequest = mockWebServer.takeRequest()
    assertThat(recordedRequest.path).isEqualTo("/test")
    assertThat(response.code).isEqualTo(200)
}

这里有个细节:mockWebServer.takeRequest() 会阻塞直到有请求到达。如果超时,它会抛出异常。所以如果你的拦截器里有异步逻辑,记得设置超时时间。

注意:测试拦截器时,不要用真实的Log类。建议用 Timber 或者自己封装一个可替换的日志接口,这样测试里可以注入mock的日志实现,验证日志内容是否正确。

知识体系总览

下面这张图,是我自己梳理的网络层测试核心脉络。你看一眼,心里就有谱了:

网络层测试 MockWebServer搭建 API接口响应测试 网络错误处理 JSON解析测试 拦截器与日志测试 综合实战案例 每个分支都可以独立测试,也可以组合使用 核心原则:可控、可重复、快速反馈

写在最后

网络层测试,说白了就是给你的API通信上「保险」。MockWebServer让这个保险变得简单、可靠。我个人建议:每个API接口至少写三个测试——成功响应、错误码处理、JSON解析异常。这样你的网络层就稳如磐石了。

嗯,今天就聊到这儿。记住:测试不是负担,是让你睡得安稳的枕头。


公众号:蓝海资料掘金营,微信deep3321