如何使用 zio-test 测试异常情况

How to test an exception case with zio-test

我有以下功能,我想测试一下:

def people(id: Int): RIO[R, People]

此功能 returns 人们如果有一个 id,resp。如果没有则失败,例如:

IO.fail(ServiceException(s"No People with id $id"))

快乐案例有效,例如:

suite("Get a Person for an ID") (
    testM("get Luke Skywalker") {
      for {
        peopleRef <- Ref.make(Vector(People()))
        luke <- Swapi.>.people(1).provide(Test(peopleRef))
      } yield assert(luke, equalTo(People()))
    },

但是如何测试失败案例呢?我尝试了不同的东西,主要是类型不匹配。这是一个尝试:

    testM("get not existing People") {
      (for {
        peopleRef <- Ref.make(Vector(People()))
        failure = Swapi.>.people(2).provide(Test(peopleRef))
      } yield assertM(failure, fail(Cause.die(ServiceException(s"No People with id 2")))
    }
  )

在@adamfraser 的帮助下 ZIO-Chat:

Basically call run on your failing effect and then assert that it is a failure with Assertion.fails. Or Assertion.dies if it is an unchecked exception.

我想我现在有一个很好的解决方案。

testM("get not existing People") {
    for {
      peopleRef <- Ref.make(Vector(People()))
      failure <- Swapi.>.people(2).provide(Test(peopleRef)).run
    } yield assert(
      failure,
      fails(equalTo(ServiceException("No People with id 2")))
    )
  }

仍然欢迎其他解决方案。

我想你肯定明白了。对于有类似问题的其他人,我唯一要补充的是您的示例还涉及环境类型,这是一个很好的讨论主题,但在某种程度上独立于如何使用 ZIO 测试来测试效果是否按预期失败。

我在下面提供了一个最小示例,说明如何测试效果是否按预期失败。如上所述,您将对效果调用 run 以获取退出值,然后使用 Assertion.fails 断言效果因检查异常而失败,Assertion.dies 断言效果失败出现未经检查的异常,或 Assertion.interrupted 测试效果是否被中断。

另请注意,您不必使用 include equalTo("fail")。如果你只关心效果失败,你可以使用 fails(anything)。如果您正在测试一个效果是否因指定的异常而消失,您可以执行类似 dies(isSubtype[IllegalArgumentException]).

的操作

希望对您有所帮助!

import zio.test._
import zio.test.Assertion._
import zio.ZIO

object ExampleSpec
    extends DefaultRunnableSpec(
      suite("ExampleSpec")(
        testM("Example of testing for expected failure") {
          for {
            result <- ZIO.fail("fail")
          } yield assert(result, fails(equalTo("fail")))
        }
      )
    )

您还可以翻转错误和结果通道:

import zio.test._
import zio.test.Assertion._
import zio.ZIO

object ExampleSpec
    extends DefaultRunnableSpec(
      suite("ExampleSpec")(
        testM("Example of testing for expected failure") {
          for {
            result <- ZIO.fail("fail").flip
          } yield assert(result, equalTo("fail"))
        }
      )
    )

这是另一个使用 assertM 的紧凑变体:

object ExampleSpec extends DefaultRunnableSpec {
  def spec = suite("ExampleSpec")(
    testM("Example of testing for expected failure") {
      assertM(ZIO.fail("fail").run)(fails(equalTo("fail")))
    }
  )
}

如果您的错误是可抛出的,则 equalsTo 在针对 运行 效果运行时会失败,因此您必须使用 isSubtype 断言来检查您是否收到正确的错误这有点棘手:

import zio.test._
import zio.test.Assertion._
import zio.ZIO

object ExampleSpec
    extends DefaultRunnableSpec(
      suite("ExampleSpec")(
        testM("Example of testing for expected failure") {
          for {
            result <- ZIO.fail(new NoSuchElementException).run
          } yield assert(result, fails(isSubtype[NoSuchElementException](anything)))
        }
      )
    )

ZIO 2.0有一些变化:

  • 使用exit代替run
  • 使用test代替testM
  • assert 有一个新的柯里化语法 assert(result)(assertion)
import zio.test._
import zio.test.Assertion._
import zio.ZIO

object ExampleSpec extends DefaultRunnableSpec(
  suite("ExampleSpec")(
    test("Example of testing for expected failure") {
      for {
        result <- ZIO.fail("failureResult").exit
      } yield assert(result)(
          fails(equalTo("failureResult"))
        )
    }
  )
)