4

我试图找出为支持两个端点的 Http4s 应用程序编写集成测试的习惯用法。我通过在新光纤上分叉它来启动Main应用程序类,然后在发布. 然后我将其转换为 a并通过具有多个s的整体传递它。ZManagedZManagedZLayerprovideCustomLayerShared()suitetestM

  1. 我在正确的轨道上吗?
  2. 它的行为不像我预期的那样:
  • 尽管以上述方式管理的 httpserver 已提供给包含两个测试的套件,但它在第一次测试后被释放,因此第二次测试失败
  • 测试套件永远不会完成,只是在执行两个测试后挂起

为下面的代码半生不熟的性质道歉。

object MainTest extends DefaultRunnableSpec {

  def httpServer =
    ZManaged
      .make(Main.run(List()).fork)(fiber => {
        //fiber.join or Fiber.interrupt will not work here, hangs the test
        fiber.interruptFork.map(
          ex => println(s"stopped with exitCode: $ex")
        )
      })
      .toLayer

  val clockDuration = 1.second

  //did the httpserver start listening on 8080?
  private def isLocalPortInUse(port: Int): ZIO[Clock, Throwable, Unit] = {
    IO.effect(new Socket("0.0.0.0", port).close()).retry(Schedule.exponential(clockDuration) && Schedule.recurs(10))
  }

  override def spec: ZSpec[Environment, Failure] =
    suite("MainTest")(
      testM("Health check") {
        for {
          _ <- TestClock.adjust(clockDuration).fork
          _ <- isLocalPortInUse(8080)
          client <- Task(JavaNetClientBuilder[Task](blocker).create)
          response <- client.expect[HealthReplyDTO]("http://localhost:8080/health")
          expected = HealthReplyDTO("OK")
        } yield assert(response) {
          equalTo(expected)
        }
      },
      testM("Distances endpoint check") {
        for {
          _ <- TestClock.adjust(clockDuration).fork
          _ <- isLocalPortInUse(8080)
          client <- Task(JavaNetClientBuilder[Task](blocker).create)
          response <- client.expect[DistanceReplyDTO](
            Request[Task](method = Method.GET, uri = uri"http://localhost:8080/distances")
              .withEntity(DistanceRequestDTO(List("JFK", "LHR")))
          )
          expected = DistanceReplyDTO(5000)
        } yield assert(response) {
          equalTo(expected)
        }
      }
    ).provideCustomLayerShared(httpServer)
}

测试的输出是第二个测试失败,而第一个测试成功。我进行了足够多的调试,发现 HTTPServer 在第二次测试之前已经关闭。

stopped with exitCode: ()
- MainTest
  + Health check
  - Distances endpoint check
    Fiber failed.
    A checked error was not handled.
    org.http4s.client.UnexpectedStatus: unexpected HTTP status: 404 Not Found

并且无论我是否在 sbt testOnly 上运行 Intellij 的测试,测试过程都会在这一切之后一直挂起,我必须手动终止它。

4

2 回答 2

2

我认为这里有两点:

ZManaged 和获取

的第一个参数ZManaged.makeacquire创建资源的函数。问题是资源获取(以及释放它们)是不间断地完成的。并且无论何时执行 a .fork,分叉的光纤都会从其父光纤继承其可中断性。所以这Main.run()部分实际上永远不会被打断。

为什么当你这样做时它似乎工作fiber.interruptForkinterruptFork实际上并没有等待光纤被中断。只有interrupt会这样做,这就是它会挂起测试的原因。

幸运的是,有一种方法可以完全满足您的要求:Main.run(List()).forkManaged. 这将生成一个ZManaged将启动主函数并在资源释放时中断它。

这是一些很好地演示了该问题的代码:

import zio._
import zio.console._
import zio.duration._

object Main extends App {

  override def run(args: List[String]): URIO[ZEnv, ExitCode] = for {
    // interrupting after normal fork
    fiberNormal <- liveASecond("normal").fork
    _           <- fiberNormal.interrupt

    // forking in acquire, interrupting in relase
    _ <- ZManaged.make(liveASecond("acquire").fork)(fiber => fiber.interrupt).use(_ => ZIO.unit)

    // fork into a zmanaged
    _ <- liveASecond("forkManaged").forkManaged.use(_ => ZIO.unit)

    _ <- ZIO.sleep(5.seconds)
  } yield ExitCode.success

  def liveASecond(name: String) = (for {
    _ <- putStrLn(s"born: $name")
    _ <- ZIO.sleep(1.seconds)
    _ <- putStrLn(s"lived one second: $name")
    _ <- putStrLn(s"died: $name")
  } yield ()).onInterrupt(putStrLn(s"interrupted: $name"))

}

这将给出输出:

born: normal
interrupted: normal

born: acquire
lived one second: acquire
died: acquire

born: forkManaged
interrupted: forkManaged

normal如您所见,两者forkManaged都会立即被打断。但是内分叉的那个acquire运行完成。

第二次测试

第二个测试似乎失败不是因为服务器关闭,而是因为服务器似乎缺少 http4s 端的“距离”路由。我注意到你得到一个 404,这是一个 HTTP 状态码。如果服务器关闭,您可能会得到类似Connection Refused. 当您收到 404 时,实际上是某个 HTTP 服务器正在响应。

所以在这里我的猜测是这条路线真的不见了。也许检查路线定义中的拼写错误,或者路线可能没有组成主路线。

于 2020-09-25T10:25:06.513 回答
0

最后@felherMain.run(List()).forkManaged帮助解决了第一个问题。

通过将方法更改为 POST 来解决关于 GET 的第二个问题,其中包含从集成测试内部被拒绝的主体。我没有进一步研究为什么 GET 从测试内部被拒绝,但不是在对正在运行的应用程序进行正常 curl 完成时。

于 2020-09-27T07:26:45.047 回答