You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OkHttp MockWebServer新实例无法接受连接问题求助

问题诊断与解决方案

看起来你遇到的问题根源在于RxJava异步操作和测试生命周期的时序不匹配,具体细节如下:

  • 第一个测试类是UI测试,Espresso框架会自动等待所有UI相关的异步操作完成后才结束测试方法,所以@After注解的tearDown会在测试逻辑真正收尾后才关闭MockWebServer,不会出现异常。
  • 第二个测试类是纯服务层测试,你用subscribeOn(Schedulers.io())发起了异步订阅,但测试方法testSuccessResponse会在订阅发出后立刻执行完毕,直接触发@After的tearDown关闭服务器。而这时候RxJava的异步请求才刚要发送,自然就会碰到Socket closed的错误。

解决方法1:用CountDownLatch阻塞测试线程,等待异步回调完成

修改testSuccessResponse方法,通过CountDownLatch让测试线程等待订阅回调执行完毕:

@Test
@Throws(Exception::class)
fun testSuccessResponse() {
    // 初始化CountDownLatch,计数为1,等待1次回调触发
    val latch = CountDownLatch(1)
    
    //Given
    server.enqueue(MockResponse()
        .setResponseCode(200)
        .setBody("{...}")
    )
    
    loginService.login("login", "password")
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
            { 
                // 根据实际业务逻辑调整断言
                Assert.assertTrue(true)
                latch.countDown() // 回调完成,释放计数
            },
            { 
                Assert.fail("请求失败:${it.message}")
                latch.countDown() // 即使失败也要释放计数,避免测试永久阻塞
            }
        )
    
    // 阻塞测试线程,最多等待5秒(可根据实际场景调整时长)
    val awaitSuccess = latch.await(5, TimeUnit.SECONDS)
    Assert.assertTrue("异步请求超时", awaitSuccess)
}

解决方法2:改用blockingSubscribe同步执行订阅

如果不需要在主线程观察结果,可以去掉observeOn(AndroidSchedulers.mainThread()),改用blockingSubscribe让订阅同步执行,测试方法会等待操作完成后再继续执行:

@Test
@Throws(Exception::class)
fun testSuccessResponse() {
    //Given
    server.enqueue(MockResponse()
        .setResponseCode(200)
        .setBody("{...}")
    )
    
    loginService.login("login", "password")
        .subscribeOn(Schedulers.io())
        .blockingSubscribe(
            { 
                // 根据实际业务逻辑调整断言
                Assert.assertTrue(true)
            },
            { 
                Assert.fail("请求失败:${it.message}")
            }
        )
    // 到这里订阅已经完成,tearDown不会提前执行
}

额外优化建议

两个测试类都修改了全局静态变量ApplicationConstants.APP_BASE_URL,建议在tearDown里恢复这个变量的原始值,避免测试类之间互相干扰:

private lateinit var originalBaseUrl: String

@Before
override fun setUp() {
    super.setUp()
    originalBaseUrl = ApplicationConstants.APP_BASE_URL // 保存原始地址
    server = MockWebServer()
    server.start()
    ApplicationConstants.APP_BASE_URL = server.url("/localhost/").toString()
    loginService = ServiceGenerator.createService(LoginService::class.java)
}

@After
@Throws(Exception::class)
override fun tearDown() {
    server.shutdown()
    ApplicationConstants.APP_BASE_URL = originalBaseUrl // 恢复原始地址
}

内容的提问来源于stack exchange,提问作者Maurice

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:56:04