网络层测试: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的日志实现,验证日志内容是否正确。
知识体系总览
下面这张图,是我自己梳理的网络层测试核心脉络。你看一眼,心里就有谱了:
写在最后
网络层测试,说白了就是给你的API通信上「保险」。MockWebServer让这个保险变得简单、可靠。我个人建议:每个API接口至少写三个测试——成功响应、错误码处理、JSON解析异常。这样你的网络层就稳如磐石了。
嗯,今天就聊到这儿。记住:测试不是负担,是让你睡得安稳的枕头。