Статьи

Тестирование исключений в Kotlin с assertFailsWith

Я хотел написать этот короткий пост, чтобы выделить функцию assertFailsWith доступную Kotlin, которая немного облегчает тестирование исключений. Тестирование исключений не является чем-то необычным или новым для языков JVM (отныне я буду использовать Java для сравнений), но Kotlin предоставляет приятное дополнительное преимущество, заключающееся в предоставлении этой функциональности в составе стандартной библиотеки. Сравнивая это с Java, вы, вероятно, включите AssertJ в смесь, чтобы достичь аналогичных результатов.

Основная цель этого поста — assertFailsWith функцией assertFailsWith . Лично я не знал, что он существует некоторое время, и по умолчанию зависел от AssertJ. Не то чтобы я имел что-то против AssertJ. Есть много других функций, которые предоставляет библиотека, но для этого конкретного экземпляра можно было бы удалить его (при условии, что вы не используете его для чего-либо еще).

Что хорошего в assertFailsWith и AssertJ в целом? Он обеспечивает лучшее тестирование исключений, чем простые конструкции, которые предоставляет JUnit. Точнее, он позволяет вам указать, какую часть вашего теста вы ожидаете, что будет сгенерировано исключение, вместо того, чтобы объявлять, что исключение возникнет где-то в коде. Это может привести к тому, что исключения будут некорректно проглочены тестом в неправильной точке и заставят вас думать, что это работает так, как вы думаете.

Теперь у меня есть краткое объяснение, давайте продолжим с основным содержанием этого поста. Ниже assertFailsWith как выглядит assertFailsWith внутри теста:

1
2
3
4
@Test
fun `calling hereIsAnException will return an exception no matter what`() {
  assertFailsWith<IllegalArgumentException> { hereIsAnException() }
}

В этом примере hereIsAnException помещается внутри тела assertFailsWith , который проверяет, что выбрасывается IllegalArgumentException . Если кто-то не поднят, то утверждение не будет выполнено. Если это произойдет, то утверждение пройдет, и исключение будет перехвачено.

Перехват исключения позволяет продолжить выполнение тестового кода, если это необходимо, а также позволяет делать дополнительные утверждения о состоянии исключения.

Например, является ли это оберткой вокруг другого исключения (каков тип его свойства cause )?

1
2
3
4
5
@Test
fun `original cause for exception was IndexOutOfBoundsException`() {
  val exception = assertFailsWith<IllegalArgumentException> { hereIsAnException() }
  assertTrue(exception.cause is IndexOutOfBoundsException)
}

Является ли сообщение тем, что вы ожидаете (не самая надежная из проверок)?

1
2
3
4
5
@Test
fun `exception has the correct message`() {
  val exception = assertFailsWith<IllegalArgumentException> { hereIsAnException() }
  assertEquals("I am a failure...", exception.message)
}

assertFailsWith только те исключения, которые относятся к тому же типу или assertFailsWith который указан assertFailsWith . Любые другие вызовут сбой теста. Так как он ловит RuntimeException только Exception или RuntimeException . Постарайтесь быть точным, чтобы ваши тесты были максимально полезными.

Как уже assertFailsWith ранее, assertFailsWith будет перехватывать только исключение, которое выбрасывается в теле функции. Поэтому, если это было написано вместо:

1
2
3
4
5
@Test
fun `calling hereIsAnException will return an exception no matter what`() {
  hereIsAnException()
  assertFailsWith<IllegalArgumentException> { hereIsAnException() }
}

Тест провалится. hereIsAnException исключение, которое не было hereIsAnException и приводит к hereIsAnException теста. Я считаю, что это лучшая часть такого рода функций по сравнению с предыдущими способами (например, утверждение внутри @Test том, что произойдет исключение).

1
2
3
4
@Test
fun `calling hereIsAnException will return an exception no matter what`() {
  assertFailsWith<IllegalArgumentException>("This should throw an illegal argument exception") { hereIsANormalReturnValue() }
}

Лично я никогда не использовал часть сообщения в утверждении. Может быть, так и есть, поэтому я подумал, что хотя бы дам вам знать.

Прежде чем я завершу небольшое количество контента в этом посте, давайте кратко рассмотрим AssertJ, чтобы мы могли провести сравнение между ними. Опять же, это только для случая перехвата исключений, который является лишь небольшой частью того, что предоставляет AssertJ.

1
2
3
4
5
6
@Test
fun `calling hereIsAnException will return an exception no matter what`() {
  assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
    hereIsAnException()
  }
}

Это немного более «многословно», чем версия assertFailsWith . Но это компенсируется множеством функций, которые предоставляет AssertJ, что делает любую дальнейшую проверку возвращенного исключения намного проще. Точнее, при использовании assertFailsWith мне нужно было написать еще одно утверждение для проверки сообщения. В AssertJ это просто функция, прикованная к концу предыдущего вызова.

В заключение, assertFailsWith — приятная небольшая функция, используемая при тестировании, чтобы гарантировать, что часть кода assertFailsWith исключение определенного типа. Он встроен в стандартную библиотеку Kotlin, что устраняет необходимость вносить дополнительную зависимость в ваш проект. При этом, это относительно простая функция, которая не предоставляет такой функциональности, как библиотека вроде AssertJ. Скорее всего, этого будет достаточно, пока вы не захотите написать тесты, которые содержат широкий диапазон или утверждения, так как это может привести к путанице.

Официальные документы для assertFailsWith можно найти здесь, если вы заинтересованы Kotlin Docs — assertFailsWith .

Опубликовано на Java Code Geeks с разрешения Дэна Ньютона, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Тестирование исключений в Kotlin с помощью assertFailsWith

Мнения, высказанные участниками Java Code Geeks, являются их собственными.