Как проверить случай исключения с помощью zio-test

У меня есть следующая функция, которую я хочу протестировать:

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

Эта функция возвращает людей, если для этого есть id, соотв. не работает, если нет, например:

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")))
    }
  )

7 ответов

Решение

Я думаю, ты определенно понял. Единственное, что я хотел бы добавить для других с подобными вопросами, это то, что ваш пример также включает тип среды, который является отличной темой для обсуждения, но в некоторой степени не зависит от того, как проверить, что эффект терпит неудачу, как ожидалось, с помощью ZIO Test.

Ниже я привел минимальный пример того, как проверить, что эффект не работает должным образом. Как упоминалось выше, вы бы позвонили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")))
        }
      )
    )

Вот еще один компактный вариант с использованием assertM:

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

А для 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"))
        )
    }
  )
)

если ваша ошибка выбрасывается, 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)))
        }
      )
    )

С помощью @adamfraser в ZIO -Chat:

Обычно вызывайте run для вашего эффекта сбоя, а затем утверждайте, что это сбой с Assertion.fails. Или Assertion.dies, если это непроверенное исключение.

Думаю, теперь у меня есть хорошее решение.

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")))
    )
  }

Другие решения по-прежнему приветствуются.

Вы также можете перевернуть каналы ошибок и результатов:

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"))
        }
      )
    )

Поэтому я решил, что ни один из ответов не был таким ясным, как мне хотелось, и чат gpt использовал тест Scala, смешанный с ZIO (LOL) (ZIO 2.0)

Я считаю, что спрашивающий просит не только проверить, не удалось ли это сделать, но и подтвердить, что сообщение соответствует их ожиданиям. Тогда большие утверждения

failsиhasMessage(в сочетании сequalTo). Было бы неплохо, если бы в документации было больше подобных примеров, а пока я надеюсь, что это поможет.

      import zio.{Scope, ZIO}
import zio.test.Assertion.{equalTo, fails, hasMessage}
import zio.test.{Spec, TestEnvironment, ZIOSpecDefault, assertZIO}

object ExampleSpec extends ZIOSpecDefault{
  override def spec: Spec[TestEnvironment with Scope, Any] = {
    suite("mapUpdatedMediaElements") {
      test("empty map of responses") {
        assertZIO(ZIO.fail(new RuntimeException("BROK3N")).exit)(fails(hasMessage(equalTo("BROK3N"))))
      }
    }
  }

}
Другие вопросы по тегам