JUnit 5: Как утверждать, исключение брошено?
Есть ли лучший способ утверждать, что метод вызывает исключение в JUnit 5?
В настоящее время я должен использовать @Rule, чтобы проверить, что мой тест выдает исключение, но это не работает в тех случаях, когда я ожидаю, что несколько методов выдают исключения в моем тесте.
14 ответов
Ты можешь использовать assertThrows()
, что позволяет тестировать несколько исключений в рамках одного теста. Благодаря поддержке лямбд в Java 8 это, вероятно, станет каноническим способом проверки исключений в JUnit.
Согласно документам JUnit:
import static org.junit.jupiter.api.Assertions.assertThrows;
...
@Test
void exceptionTesting() {
Executable closureContainingCodeToTest = () -> throw new IllegalArgumentException("a message");
assertThrows(IllegalArgumentException.class, closureContainingCodeToTest, "a message");
}
В Java 8 и JUnit 5 (Юпитер) мы можем утверждать исключения следующим образом. С помощью org.junit.jupiter.api.Assertions.assertThrows
public static
T assertThrows(класс Ожидаемый тип, исполняемый файл) Утверждает, что выполнение предоставленного исполняемого файла вызывает исключение ожидаемого типа и возвращает исключение.
Если не сгенерировано исключение или если сгенерировано исключение другого типа, этот метод завершится ошибкой.
Если вы не хотите выполнять дополнительные проверки экземпляра исключения, просто проигнорируйте возвращаемое значение.
@Test
public void itShouldThrowNullPointerExceptionWhenBlahBlah() {
assertThrows(NullPointerException.class,
()->{
//do whatever you want to do here
//ex : objectName.thisMethodShoulThrowNullPointerExceptionForNullParameter(null);
});
}
Этот подход будет использовать функциональный интерфейс Executable
в org.junit.jupiter.api
,
См.:
Они изменили его в JUnit 5 (ожидается: InvalidArgumentException, фактический: вызванный метод), и код выглядит так:
@Test
public void wrongInput() {
Throwable exception = assertThrows(InvalidArgumentException.class,
()->{objectName.yourMethod("WRONG");} );
}
Вы можете использовать
assertThrows()
, Но с
assertThrows
ваше утверждение пройдет, даже если выброшенное исключение имеет дочерний тип.
Это потому, что JUnit 5 проверяет тип исключения, вызывая
Class.isIntance(..)
,
Class.isInstance(..)
вернет истину, даже если выброшенное исключение относится к дочерним типам.
Обходной путь для этого - утверждение класса:
Throwable throwable = assertThrows(Throwable.class, () -> {
service.readFile("sampleFile.txt");
});
assertEquals(FileNotFoundException.class, throwable.getClass());
Теперь Junit5 предоставляет возможность утверждать исключения
Вы можете протестировать как общие исключения, так и настраиваемые исключения
Общий сценарий исключения:
ExpectGeneralException.java
public void validateParameters(Integer param ) {
if (param == null) {
throw new NullPointerException("Null parameters are not allowed");
}
}
ExpectGeneralExceptionTest.java
@Test
@DisplayName("Test assert NullPointerException")
void testGeneralException(TestInfo testInfo) {
final ExpectGeneralException generalEx = new ExpectGeneralException();
NullPointerException exception = assertThrows(NullPointerException.class, () -> {
generalEx.validateParameters(null);
});
assertEquals("Null parameters are not allowed", exception.getMessage());
}
Вы можете найти образец для тестирования CustomException здесь: пример кода исключения assert
ExpectCustomException.java
public String constructErrorMessage(String... args) throws InvalidParameterCountException {
if(args.length!=3) {
throw new InvalidParameterCountException("Invalid parametercount: expected=3, passed="+args.length);
}else {
String message = "";
for(String arg: args) {
message += arg;
}
return message;
}
}
ExpectCustomExceptionTest.java
@Test
@DisplayName("Test assert exception")
void testCustomException(TestInfo testInfo) {
final ExpectCustomException expectEx = new ExpectCustomException();
InvalidParameterCountException exception = assertThrows(InvalidParameterCountException.class, () -> {
expectEx.constructErrorMessage("sample ","error");
});
assertEquals("Invalid parametercount: expected=3, passed=2", exception.getMessage());
}
Ты можешь использовать assertThrows()
, Мой пример взят из документов http://junit.org/junit5/docs/current/user-guide/
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
....
@Test
void exceptionTesting() {
Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("a message");
});
assertEquals("a message", exception.getMessage());
}
Я думаю, что это еще более простой пример
List<String> emptyList = new ArrayList<>();
Optional<String> opt2 = emptyList.stream().findFirst();
assertThrows(NoSuchElementException.class, () -> opt2.get());
призвание get()
на необязательный, содержащий пустой ArrayList
бросит NoSuchElementException
, assertThrows
объявляет ожидаемое исключение и предоставляет лямбда-поставщика (не принимает аргументов и возвращает значение).
Спасибо @prime за его ответ, который я надеюсь развить.
Еще проще one liner. В этом примере с использованием Java 8 и JUnit 5 лямбда-выражения или фигурные скобки не требуются.
import static org.junit.jupiter.api.Assertions.assertThrows;
@Test
void exceptionTesting() {
assertThrows(MyException.class, myStackObject::doStackAction, "custom message if assertion fails...");
// note, no parenthesis on doStackAction ex ::pop NOT ::pop()
}
Мое решение:
protected <T extends Throwable> void assertExpectedException(ThrowingRunnable methodExpectedToFail, Class<T> expectedThrowableClass,
String expectedMessage) {
T exception = assertThrows(expectedThrowableClass, methodExpectedToFail);
assertEquals(expectedMessage, exception.getMessage());
}
И вы можете назвать это так:
assertExpectedException(() -> {
carService.findById(id);
}, IllegalArgumentException.class, "invalid id");
This is what I do when testing to make sure an exception has been thrown
//when
final var tripConsumer = new BusTripConsumer(inputStream);
final Executable executable = () -> tripConsumer.deserialiseTripData();
//then
assertThrows(IllegalArgumentException.class, executable);
На самом деле я думаю, что есть ошибка в документации для этого конкретного примера. Метод, который предназначен, является ожидаемым
public static void assertThrows(
public static <T extends Throwable> T expectThrows(
Хорошая практика кодирования - генерировать исключения, когда данные недействительны. Лучший тестовый пример - проверить, было ли выбрано исключение для недопустимого аргумента. Для тестирования обоих сценариев JUnit 5 предоставляет нам методы assertThrows и assertDoesNotThrow .
public class AppleCalculator {
public String getError(String apple) {
if (Objects.isNull(apple)) {
throw new NullPointerException("apple cannot be null");
}
return apple;
}
}
@Test
void assertThrowTest() {
AppleCalculator appleCalculator = new AppleCalculator();
assertThrows(NullPointerException.class, () -> appleCalculator.getError(null));
assertThrows(NullPointerException.class, () -> appleCalculator.getError(null), "it will throw exception");
Supplier<String> messageSupplier = () -> "it will throw exception";
assertThrows(NullPointerException.class, () -> appleCalculator.getError(null), messageSupplier);
Exception exception = assertThrows(NullPointerException.class, () -> appleCalculator.getError(null), messageSupplier);
assertEquals("apple cannot be null", exception.getMessage());
}
@Test
void assertDoesNotThrowTest() {
AppleCalculator appleCalculator = new AppleCalculator();
assertDoesNotThrow(() -> appleCalculator.getError("apple"));
assertDoesNotThrow(() -> appleCalculator.getError("apple"), "it will not throw exception");
Supplier<String> messageSupplier = () -> "it will not throw exception";
String response = assertDoesNotThrow(() -> appleCalculator.getError("apple"), messageSupplier);
assertEquals("apple", response);
}
Исключение, объявленное в assertThrows (ExceptionType.class,..), должно быть сгенерировано, если выброшенное исключение отличается или исключение не сгенерировано вообще, тогда тестовый пример завершится ошибкой.
- если assertDoesNotThrow обнаружит исключение, тестовый пример завершится ошибкой.
- Исключение, объявленное в assertThrows (ExceptionType.class,..), будет включать все его дочернее исключение, т.е. если объявлено родительское исключение и тестовый пример вызывает дочернее исключение, тестовый пример все равно будет считаться пройденным.
Есть 3 способа заявить об определенном исключении в Junit. Давайте напишем кейсы для него.
1. идиома try-catch Эта идиома является одной из самых популярных, потому что она использовалась уже в JUnit 3. Этот подход является распространенным паттерном. Тест завершится неудачей, если не сгенерировано исключение, а само исключение проверено в предложении catch.
@Test
public void convertIntoUpperCase_withInvalidInput_tryCatchIdiom() {
try {
exceptionHandling.convertIntoUpperCase("");
fail("It should throw IllegalArgumentException");
} catch (IllegalArgumentException e) {
Assertions.assertThat(e)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Empty value is passed.");
}
}
2. Ожидаемая аннотация @Test В этом подходе мы указываем ожидаемое исключение в @Test, как показано ниже @Test(Ожидаемый = IllegalArgumentException.class)
Когда исключение не было выдано, вы получите следующее сообщение: java.lang.AssertionError: Ожидаемое исключение: java.lang.IllegalArgumentException
При таком подходе вы должны быть осторожны. Иногда бывает заманчиво ожидать общее исключение, RuntimeException или даже Throwable. И это считается плохой практикой, потому что ваш код может вызвать исключение в других местах, чем вы ожидали, и ваш тест все равно пройдет!
Одним из недостатков этого подхода является то, что вы не можете утверждать сообщение об исключении.
@Test(expected = IllegalArgumentException.class)
public void convertIntoUpperCase_withInvalidInput_testExpected() {
exceptionHandling.convertIntoUpperCase("");
}
3. Junit @Rule Тот же пример может быть создан с использованием правила ExceptedException. Правило должно быть открытым полем, помеченным аннотацией @Rule.
@Test
public void convertIntoUpperCase_withInvalidInput_ExpectedExceptionRule() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("Empty value is passed.");
exceptionHandling.convertIntoUpperCase("");
}
Я нахожу приведенный выше код более читабельным, поэтому я предпочитаю использовать этот подход.
Когда исключение не выдается, вы получите следующее сообщение: java.lang.AssertionError: Ожидаемый тест для выброса (экземпляр java.lang.IllegalArgumentException и исключение с сообщением "Пустое значение передано".). Довольно мило
Но не все исключения я проверяю с помощью вышеуказанного подхода. Иногда мне нужно проверить только тип создаваемого исключения, а затем я использую аннотацию @Test.
Вот простой способ.
@Test
void exceptionTest() {
try{
model.someMethod("invalidInput");
fail("Exception Expected!");
}
catch(SpecificException e){
assertTrue(true);
}
catch(Exception e){
fail("wrong exception thrown");
}
}
Успешно, только когда вы ожидаете исключения.