Java assert with exception

Java assert with exception

This post explores some techniques for asserting exceptions in Java with JUnit.

Table of contents

Using try — catch with fail()

In this approach, the code which is excepted to throw an exception is wrapped in a try — catch block.

Then the fail() method is called immediately after the code that should throw the exception, so that if the exception is not thrown, the test fails. Then assertions can be performed on the exception that has been caught:

import org.junit.Test; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; public class UsingTryCatchWithFail  private Foo foo = new Foo(); @Test public void doStuff_shouldThrowException()  try  // This method is expected to throw a FooException foo.doStuff(); // If the exception is not thrown, the test will fail fail("Expected exception has not been thrown"); > catch (FooException e)  assertThat(e.getMessage(), is("An exception has occurred")); assertThat(e.getCause(), instanceOf(IllegalStateException.class)); > > > 

Using @Test with expected

In this approach, the @Test annotation is used to indicate the expected exception to be thrown in the test:

import org.junit.Test; public class UsingTestWithExpected  private Foo foo = new Foo(); @Test(expected = FooException.class) public void doStuff_shouldThrowException()  foo.doStuff(); > > 

While it’s a simple approach, it lacks the ability of asserting both the message and the cause of the exception that has been thrown. As good exception messages are valuable, assertions on messages should be taken into account.

Also, depending on how the test is written, this approach should be discouraged: As the exception expectation is placed around the whole test method, this might not actually test what is itended to be tested, leading to false positives results, as shown below:

@Test(expected = FooException.class) public void prepareToDoStuff_shouldSucceed_doStuff_shouldThrowException()  // This method may throw a FooException, which may lead to a false positive result foo.prepareToDoStuff(); // This is the method that is supposed to throw the actual FooException being asserted foo.doStuff(); > 

Using @Rule with ExpectedException

This approach uses the ExpectedException rule to assert an exception and also gives the ability of making assertions on both the message and the cause of the exeption:

import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import static org.hamcrest.core.IsInstanceOf.instanceOf; public class UsingRuleWithExpectedException  private Foo foo = new Foo(); @Rule public final ExpectedException thrown = ExpectedException.none(); @Test public void doStuff_shouldThrowException()  thrown.expect(FooException.class); thrown.expectMessage("An exception has occurred"); thrown.expectCause(instanceOf(IllegalStateException.class)); foo.doStuff(); > > 

While this approach attempts to fix the caveats of @Test with expected to assert the exception message and cause, it also has issues when it comes to false positives:

@Test public void prepareToDoStuff_shouldSucceed_doStuff_shouldThrowException()  thrown.expect(FooException.class); thrown.expectMessage("An exception has occurred"); thrown.expectCause(instanceOf(IllegalStateException.class)); // This method may throw a FooException, which may lead to a false positive result foo.prepareToDoStuff(); // This is the method that is supposed to throw the actual FooException being asserted foo.doStuff(); > 

Finally, if the test follows Behaviour-driven Development (BDD), you’ll find that ExpectedException doesn’t use such writing style.

Using assertThrows from JUnit 5

JUnit 5 aims to solve some problems of JUnit 4 and also takes advantage of Java 8 features, such as lambdas.

When it comes to exceptions, the @Test annotation no longer can be used for indicate the expected exception. As described above, this approach may lead to false positives and doesn’t allow asserting on the exception itself.

As replacement, JUnit 5 introduced the assertThrows() method: It asserts that the execution of the supplied executable throws an exception of the expected type and returns the exception instance, so assertions can be performed on it.

The test will fail if no exception is thrown, or if an exception of a different type is thrown.

import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; public class UsingAssertThrowsFromJUnit5  private Foo foo = new Foo(); @Test @DisplayName("doStuff method should throw exception") public void doStuff_shouldThrowException()  Throwable thrown = assertThrows(FooException.class, () -> foo.doStuff()); assertThat(thrown.getMessage(), is("An exception has occurred")); assertThat(thrown.getCause(), instanceOf(IllegalStateException.class)); > > 

Using AssertJ

AssertJ provides a rich API for fluent assertions in Java. It aims to improve the test code readability and make the maintenance of tests easier by providing strongly-typed assertions and intuitive failure messages.

If your tests use at least Java 8, then you can use AssertJ 3.x and leverage on lambdas for asserting exceptions:

import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchThrowable; public class UsingAssertJWithJava8  private Foo foo = new Foo(); @Test public void doStuff_shouldThrowException_1()  assertThatExceptionOfType(FooException.class) .isThrownBy(() -> foo.doStuff()) .withMessage("An exception has occurred") .withCauseExactlyInstanceOf(IllegalStateException.class); > @Test public void doStuff_shouldThrowException_2()  assertThatThrownBy(() -> foo.doStuff()) .isInstanceOf(FooException.class) .hasMessage("An exception has occurred") .hasCauseExactlyInstanceOf(IllegalStateException.class); > @Test public void doStuff_shouldThrowException_3()  Throwable thrown = catchThrowable(() -> foo.doStuff()); assertThat(thrown) .isInstanceOf(Exception.class) .hasMessage("An exception has occurred") .hasCauseExactlyInstanceOf(IllegalStateException.class); > @Test public void doStuff_shouldThrowException_4()  FooException thrown = catchThrowableOfType(() -> foo.doStuff(), FooException.class); assertThat(thrown) .hasMessage("An exception has occurred") .hasCauseExactlyInstanceOf(IllegalStateException.class); > > 

If your tests use Java 7, then you can use the try — catch with fail() approach with AssertJ 2.x and perform fluent assertions on the exception that has been thrown:

import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; import static org.junit.Assert.fail; public class UsingAssertJWithJava7  private Foo foo = new Foo(); @Test public void doStuff_shouldThrowException_1()  try  foo.doStuff(); fail("Expected exception has not been thrown"); > catch (FooException e)  assertThat(e) .hasMessage("An exception has occurred") .hasCauseExactlyInstanceOf(IllegalStateException.class); > > @Test public void doStuff_shouldThrowException_2()  try  foo.doStuff(); failBecauseExceptionWasNotThrown(FooException.class); > catch (FooException e)  assertThat(e) .hasMessage("An exception has occurred") .hasCauseExactlyInstanceOf(IllegalStateException.class); > > > 

Bottom line and my thoughts

After evaluating the approaches for asserting exceptions described above, I would avoid both @Test with expected and @Rule with ExpectedException approaches, as they may lead to false positive results.

For Java 7, simply stick to the try — catch with fail() approach, even if the test look a bit clumsy.

If you are using at least Java 8 (which I really hope you are), then you can leverage the power of lambdas for assertions. And I strongly encourage you to consider using AssertJ, as it provides a fluent API and the assertions are very close to plain English, which boosts the readability of your tests.

Источник

Ожидаемое исключение JUnit 5

Это продолжение туториала по JUnit 5. Введение опубликовано здесь.

В JUnit 5, чтобы написать тестовый код, который, как ожидается, вызовет исключение, мы должны использовать Assertions.assertThrows().

В данном примере ожидается, что тестовый код в комментированном блоке вызовет исключение типа ApplicationException .

Использование Assertions.assertThrows()

@Test void testExpectedException() < ApplicationException thrown = Assertions.assertThrows(ApplicationException.class, () ->< //Code under test >); Assertions.assertEquals("some message", exception.getMessage()); >
  1. Assertions API assertThrows ().
    • Синтаксис
    • Вывод теста
  2. Ожидаемое исключение генерируется в тесте.
  3. Сгенерировано исключение другого типа, или не сгенерировано исключение

1. Assertions API assertThrows ()

1.1. Синтаксис

Метод assertThrows() утверждает, что выполнение прилагаемого исполняемого блока или лямбда — выражения вызывает исключение типа expectedType . Это перегруженный метод, который принимает следующие параметры.

static T assertThrows(Class expectedType, Executable executable) static T assertThrows(Class expectedType, Executable executable, String message) static T assertThrows(Class expectedType, Executable executable, Supplier messageSupplier)
  • expectedType — ожидается, что тестовый код вызовет исключение этого типа.
  • message — если исполняемый код не вызывает никаких исключений, это сообщение будет напечатано вместе с результатом FAIL.
  • messageSupplier — сообщение будет извлечено из него в случае неудачи теста.

1.2. Вывод теста

Если в блоке не было генерировано исключение, executable , то assertThrows() вернет FAIL .

Если выбрасывается исключение другого типа, assertThrows() будет FAIL .

Если блок кода вызывает исключение класса, который является подтипом исключения expectedType , только тогда assertThrows() вернет PASS .

Например, если мы ожидаем, IllegalArgumentException и тест выдает ошибку NumberFormatException, тогда и вывод теста будет PASS потому что NumberFormatException расширяет класс IllegalArgumentException.

Кроме того, если мы передадим Exception.class в качестве ожидаемого типа исключения, любое исключение, выброшенное из исполняемого блока, сделает результат assertion равным PASS , поскольку Exception является супертипом для всех исключений.

2. Ожидаемое исключение генерируемое в тесте

Ниже приведен очень простой тест, который ожидает, что исключение NumberFormatException будет сгенерировано при выполнении предоставленного блока кода.

@Test void testExpectedException() < NumberFormatException thrown = Assertions.assertThrows(NumberFormatException.class, () ->< Integer.parseInt("One"); >, "NumberFormatException was expected"); Assertions.assertEquals("For input string: \"One\"", thrown.getMessage()); > @Test void testExpectedExceptionWithParentType() < Assertions.assertThrows(IllegalArgumentException.class, () ->< Integer.parseInt("One"); >); >
  • В тесте testExpectedException , исполняемый код Integer.parseInt(«One») генерирует исключение NumberFormatException, если аргумент метода не является допустимым текстовым представлением числа. Метод assertThrows() ожидает это исключение, так что результат теста PASS .
  • В тесте testExpectedExceptionWithParentType , мы выполняем тот же код, но на этот раз мы принимаем исключение IllegalArgumentException , родительское для NumberFormatException . Этот тест тоже проходит.

3. Сгенерировано исключение другого типа, или не сгенерировано исключение

Если исполняемый код вызывает исключение любого другого типа, то результат теста будет FAIL .

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

Например, в приведенном ниже примере «1» это допустимое число, поэтому исключение не возникает. Этот тест завершится ошибкой с сообщением в консоли.

@Test void testExpectedExceptionFail() < NumberFormatException thrown = Assertions .assertThrows(NumberFormatException.class, () ->< Integer.parseInt("1"); >, "NumberFormatException error was expected"); Assertions.assertEquals("Some expected message", thrown.getMessage()); >

В этом посте мы узнали, как написать тест, ожидающий возникновение исключений. Эти тесты полезны при тестировании кода, написанного в блоках catch.

Источник

Читайте также:  Язык java для начинающих учить
Оцените статью