4. MockWebServer入门:添加依赖、创建MockServer、启动与关闭
搞测试最烦什么?就是依赖外部环境。网络请求一跑就崩,服务器挂了你就卡在那。我这些年干的最爽的一件事,就是学会用MockWebServer——一个轻量级的HTTP服务器,专门用来在测试里模拟后端。
说白了,你的App发请求,MockWebServer替你挡在前面。你要什么响应,就给它配什么响应。简单、可控、而且跑得飞快。
4.1 添加依赖
你用的项目如果是Kotlin + Gradle,那加一行就够了。记住,这是test依赖,别搞到主工程里去。
dependencies {
// 其他依赖...
testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
}
版本号我推荐用最新的4.x稳定版。别问我为什么不用3.x,我在项目里踩过坑——3.x的某些API在老版本OkHttp上会报ClassNotFoundException。你如果还在用OkHttp 3.x,记得看兼容表。
| MockWebServer 版本 | OkHttp 兼容版本 | 备注 |
|---|---|---|
| 4.12.0 | OkHttp 4.x (Kotlin) | 推荐 |
| 3.14.9 | OkHttp 3.x | Java版本,API略有差异 |
如果你用Maven或Gradle Kotlin DSL,语法稍有不同。但关键点一样:testCompile 'com.squareup.okhttp3:mockwebserver:4.12.0'
4.2 创建 MockServer 实例
创建起来超级简单。就是一个new操作,或者用Kotlin的构造函数。
val mockServer = MockWebServer()
等你拿到这个实例,它就是你的“私有服务器”了。端口什么的还不用管,后面启动时会自动分配。我个人习惯把这个实例声明在测试类的顶层,然后用@Before初始化。
class ApiServiceTest {
private lateinit var mockServer: MockWebServer
@Before
fun setUp() {
mockServer = MockWebServer()
mockServer.start()
}
// ... 测试用例
}
注意:每个测试方法之间最好重新创建MockWebServer实例,避免前一个测试的响应队列污染下一个测试。我见过团队为了省事只关不建,结果测试跑得乱七八糟的。
4.3 启动 MockServer
启动方法就一个:start()。它会自动选择一个可用端口(通常从1080开始往上找)。你也可以指定端口:start(port)
mockServer.start() // 随机端口
mockServer.start(9090) // 固定端口
我建议大多数时候用随机端口,因为测试环境不会有端口冲突。有一次我们在CI上并行跑测试,有人固定了端口,结果好几个job全挂了——端口被占。
启动后,你需要的其实是它的端口号,用来配置你的Retrofit Base URL:
val baseUrl = "http://localhost:${mockServer.port}/"
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(OkHttpClient.Builder().build())
.build()
核心记忆点:start() 启动服务器,mockServer.port 获取端口,enqueue() 预设响应。
4.4 关闭 MockServer
用完记得关,不然下一个测试跑的时候端口被占,或者进程残留。关闭方法:
@After
fun tearDown() {
mockServer.shutdown()
}
shutdown()会优雅地关闭:停止接收新连接,等待正在处理的请求完成(可以设超时),然后释放端口。你如果只是简单断开,直接调close()也行,但我建议用shutdown()保平安。
Kotlin用户可以考虑use函数:MockWebServer().use { server -> ... },但要注意use会自动调用close,和shutdown的区别在于,close不会等待请求处理。我一般还是手写@After更放心。
4.5 完整的开胃示例
咱们别光说不练。搞个最简单的测试,看看MockWebServer怎么运作。
class SimpleMockTest {
private lateinit var mockServer: MockWebServer
@Before
fun setUp() {
mockServer = MockWebServer()
mockServer.start()
}
@After
fun tearDown() {
mockServer.shutdown()
}
@Test
fun `mock server 返回200`() {
// 预设一个响应
mockServer.enqueue(MockResponse()
.setResponseCode(200)
.setBody("{\"message\":\"hello\"}")
.addHeader("Content-Type", "application/json"))
// 创建OkHttp客户端直接发请求(演示用)
val client = OkHttpClient()
val request = Request.Builder()
.url("http://localhost:${mockServer.port}/test")
.build()
val response = client.newCall(request).execute()
assertEquals(200, response.code())
assertTrue(response.body()?.string()?.contains("hello") ?: false)
// 可选:检查MockServer收到的请求
val recordedRequest = mockServer.takeRequest()
assertEquals("/test", recordedRequest.path)
}
}
这个例子跑完,你就理解了:start() -> enqueue() -> 发请求 -> 断言 -> shutdown()。
4.6 常见避坑
- 端口占用:我遇到过两次。一次是忘记shutdown,一次是CI上两个测试类用了同一个固定端口。解决方案:用随机端口,或者用
@Before里重新new实例。 - 响应队列用完:如果你enqueue了3个响应,但发了4个请求,第4个会收到404。所以要么数对着,要么用
mockServer.enqueue()循环加个默认的兜底。 - 多线程请求:MockWebServer是单线程处理的,如果你在测试里发了多个并发请求,注意顺序可能乱。可以用
mockServer.takeRequest(timeout, unit)配合超时处理。
我曾经在重构一个老项目时,发现某测试用例的@After里只写了mockServer.close()而不是shutdown(),结果导致后续测试端口绑定失败。排查了半天。所以记住:shutdown() > close()。
4.7 小结
好了,MockWebServer的基本操作就这些。添加依赖 → 创建实例 → start() → enqueue() → shutdown()。你会发现它比你想象中简单得多——其实就是个嵌入式HTTP服务器,只不过专门给测试用的。下一关咱们就要用它和Retrofit真正搭伙了。