无法通过TestClock推进测试ZIO HTTP客户端指数重试
ZIO HTTP客户端测试挂起问题分析
环境依赖
- ZIO HTTP 3.0.0-RC6(手动排除了间接引入的ZIO 2.0.21)
- ZIO 与 ZIO-test 2.1-RC1
- Scala 2.13
测试套件代码
object IpGetterSpec extends ZIOSpecDefault { val schedule = Schedule.exponential(10.milliseconds) && Schedule.recurs(2).tapOutput(o => ZIO.logInfo(s"retrying $o")) def spec = suite("IpGetterSpec")( test("test retry exponential") { for { fiber <- ZIO.fail("fail").retry(schedule).fork _ <- TestClock.adjust(100.milliseconds) res <- fiber.join.exit } yield assertTrue(res.isFailure) }, test("request fails after being tried three times") { for { client <- ZIO.service[Client] fiber <- client.url(URL.decode("http://127.0.0.1:80/?format=json").toOption.get).get("/").retry(schedule).fork _ <- TestClock.adjust(100.milliseconds) res <- fiber.join.exit } yield assertTrue(res.isFailure) } ).provideShared(Client.default, Scope.default) }
现象
- 第一个测试(纯ZIO失败重试逻辑验证)可正常运行,符合预期
- 第二个测试(验证HTTP请求重试逻辑,目标地址127.0.0.1:80无服务监听)使用
TestClock.adjust时无限挂起 - 将第二个测试改为使用实时时钟后,测试可正常运行:
test("request fails after being tried three times") { for { client <- ZIO.service[Client] res <- client.url(URL.decode("http://127.0.0.1:80/?format=json").toOption.get).get("/").retry(schedule).exit } yield assertTrue(res.isFailure) } @@ TestAspect.withLiveClock
问题解答
- 该现象确实与ZIO HTTP客户端有关,属于已知的测试环境交互问题
- 核心原因:ZIO HTTP客户端的底层网络操作(如TCP连接建立)依赖JDK原生IO实现,这类操作不受ZIO TestClock的管控。当使用TestClock调整时间时,重试调度的时间触发逻辑仅作用于ZIO层,但实际的网络连接尝试不会被TestClock模拟,导致fiber一直等待网络操作的结果返回,最终表现为无限挂起。
- 使用
TestAspect.withLiveClock时,重试调度基于真实时间触发,网络连接失败的错误能被正常捕获,重试逻辑按预期执行3次后失败,测试顺利完成。
内容的提问来源于stack exchange,提问作者Yar
相关产品推荐
相关产品推荐

