- JUnit Assertions
- 6.2 Методы assertEquals, assertTrue, assertFalse
- 6.3 Метод assertAll
- 6.4 Метод assertTimeout
- 6.5 Метод assertThrows
- Unit testing Exceptions in Java with JUnit
- Mocking Exceptions
- Asserting Exceptions
- Using expected param of @Test annotation
- Using try catch explicitly
- Using ExpectedException rule
- JUnit Fail Method
- Source: (Foo.java)
- Unit Test Source: (UnitTest.java)
- Output:
- Questions answered by this page:
JUnit Assertions
Ассерты (asserts) — это специальные проверки , которые можно вставить в разные места кода. Их задача определять, что что-то пошло не так. Вернее, проверять, что все идет как нужно. Вот это “как нужно” они и позволяют задать различными способами.
С некоторыми ассертами ты уже сталкивался в коде выше. Первый из них – проверка объектов на равенство. Если объекты не равны — кинется исключение и тест будет провален.
Тут важен порядок сравнения , ведь JUnit в итоговом отчете напишет что-то типа “получено значение 1, а ожидалось 3”. Общий формат такой проверки имеет вид:
assertEquals(эталон, значение)
@Test public void whenAssertingEquality ()
6.2 Методы assertEquals, assertTrue, assertFalse
Ниже я приведу список самых популярных методов — ассертов. По их названиям вполне можно догадаться как они работают. Но все же напишу краткое пояснение:
assertEquals | Проверяет, что два объекта равны |
---|---|
assertArrayEquals | Проверяет, что два массива содержат равные значения |
assertNotNull | Проверяет, что аргумент не равен null |
assertNull | Проверяет, что аргумент равен null |
assertNotSame | Проверят, что два аргумента — это не один и тот же объект |
assertSame | Проверят, что два аргумента — это один и тот же объект |
assertTrue | Проверяет, что аргумент равен true |
assertFalse | Проверяет, что аргумент равен false |
Некоторые из этих методов кажутся излишними. Зачем использовать assertSame(a, b) , если можно просто написать assertTrue(a == b) ?
Все дело в том, что assert — это очень умный метод. Он делает много чего полезного и, в том числе, пишет в лог информацию об ошибке . В первом случае он напишет, что ожидался объект А, а получен объект Б. Во втором случае просто напишет, что ожидалось true .
Когда у тебя сотни тестов, особенно выполняемые на специальном тестовом сервере, то наличие детальных логов может быть суперполезным. Думаю,ты понимаешь, о чем я.
Пример сравнения массивов:
@Test public void whenAssertingArraysEquality() < char[] expected = ; char[] actual = "Junit".toCharArray(); assertArrayEquals(expected, actual); >
6.3 Метод assertAll
Как уже говорилось выше, метод assert не просто выполняет проверку, но и пишет в лог много информации о сравнимых объектах.
Допустим выполняется сравнение:
Address address = unitUnderTest.methodUnderTest(); assertEquals("Вашингтон", address.getCity()); assertEquals("Oracle Parkway", address.getStreet()); assertEquals("500", address.getNumber());
Но если один из параметров не совпадет, то проверки остальных не произойдет. А хотелось бы чтобы они все-таки произошли и их результаты записались в лог. Но при этом, если хотя бы одна проверка не прошла, то тест все-таки был провален.
Для этого есть специальный метод — assertAll() . В качестве первого аргумента он принимает комментарий, который нужно записать в лог, а дальше — любое количество функций-ассертов.
Вот как будет переписан наш пример с его помощью:
Address address = unitUnderTest.methodUnderTest(); assertAll("Сложный сценарий сравнение адреса", () -> assertEquals("Вашингтон", address.getCity()), () -> assertEquals("Oracle Parkway", address.getStreet()), () -> assertEquals("500", address.getNumber()) );
Тогда если адрес будет неправильный, в лог будет написано что-то типа:
Сложный сценарий сравнение адреса (3 failures) expected: but was: expected: but was: expected: but was:
6.4 Метод assertTimeout
Помнишь аннотацию @Timeout ? Она позволяла контролировать время выполнения всего метода. Но иногда бывает полезно задать ограничения на выполнения некоторой части кода внутри метода. Для этого можно использовать assertTimeout() .
В качестве первого параметра в него передается время, а в качестве второго — код (функция), который должен выполниться за указанное время. Пример:
@Test public void whenAssertingTimeout() < assertTimeout( ofSeconds(2), () -> < // пауза в одну секунду Thread.sleep(1000); >); >
У класса Assert есть 12 вариантов метода assertTimeout() . Если хочешь ознакомиться с ними подробнее, добро пожаловать в официальную документацию.
6.5 Метод assertThrows
Очень часто бывают ситуации, когда тебе нужно убедиться, что в определенной ситуации код кидает нужное исключение: определил ошибку и кинул нужное исключение. Это очень распространенная ситуация.
На этот случай есть еще один полезный метод assert — это assertThrows() . Общий формат его вызова имеет вид:
assertThrows(исключение, код)
По сути, он очень похож на метод assertTimeout() , только он проверяет, чтобы указанный код выкинул нужное исключение. Пример:
@Test void whenAssertingException() < Throwable exception = assertThrows( IllegalArgumentException.class, () -> < throw new IllegalArgumentException("Exception message"); >); assertEquals("Exception message", exception.getMessage()); >
Unit testing Exceptions in Java with JUnit
I have seen people unit testing the core logic and skipping on the Exception cases of their code considering it of little importance or harder to test. In my opinion, exception cases are not of lesser importance and by no means they are harder to test.
Here we will consider many ways to mock exceptions and assert exceptions with JUnit.
Mocking Exceptions
With Mockito you can not only mock methods to return something but also you can mock them to throw exceptions using Mockito.when .
You can mock method to throw exception of some type or appropriate exception object itself.
Let’s see a class TestMe that we are testing
public class TestMe private Validator validator; public TestMe(Validator validator) this.validator = validator; > public String execute(int number) try validator.validate(number); > catch (Exception e) return null; > return String.valueOf(number); > >
Suppose we want to test a case where validation fails and the execute method returns null.
We can mock exception and test the code as follows.
public class TestMeTest @Mock Validator validator; private TestMe testMe; @Before public void setUp() throws Exception MockitoAnnotations.initMocks(this); testMe = new TestMe(validator); > @Test public void resultOfExecutingInvalidNumberIsNull() int number = -1; Mockito.doThrow(IllegalArgumentException.class).when(validator).validate(number); String result = testMe.execute(number); Assert.assertNull(result); > >
This test makes validator.validate method of mock validator to throw IllegalArgumentException on which the method returns null and we make our test expect null as a result.
You can use object of Exception instead of class in doThrow as
Mockito.doThrow(new IllegalArgumentException("No negative number")) .when(validator).validate(number);
There is another way to mock exception, that is using Mockito.when :
Mockito.when(validator.validate(number)).thenThrow(IllegalArgumentException.class);
I used to write it this way, but it does not work for mocking method that returns void . So I switched to using doThrow , doReturn and doNothing instead of using when for all the mocking.
doReturn way is more readable and less confusing if you compare it with when .
Asserting Exceptions
Sometimes your code is expected to throw exception and there is no reason to not to test that case. It’s not hard, rather it is so simple that there are 3 different ways to do it, each with it’s own pros and cons.
Consider the code below that we are testing:
public class TestMe private Validator validator; public TestMe(Validator validator) this.validator = validator; > public String perform(int number) if (number 0) throw new IllegalArgumentException("Number can not be negative"); > return String.valueOf(number); > >
We want to assert that when perform methods is called with a negative number it fails.
Let’s consider three ways to assert that case.
Using expected param of @Test annotation
@Test annotation of JUnit takes expected param of class type that extends from Throwable.
If the code inside the test throws the exception of type given in param, then the test passes otherwise it fails.
@Test(expected = IllegalArgumentException.class) public void performingNegativeNumberFailsWithExpected() testMe.perform(-1); >
- Can not test details like message of the exception
- Code written after method call perform is not executed so it prevents writing verification if we have any.
Using try catch explicitly
Inside the test itself we can invoke the method we are testing inside a try catch block and expect code to throw exception and to fail if no exception is thrown or thrown exception is of wrong type.
We use Assert.fail at places where code is not supposed to reach.
@Test public void performingNegativeNumberFailsWithTryCatch() try testMe.perform(-1); Assert.fail("Exception expected"); > catch (IllegalArgumentException expected) String message = expected.getMessage(); assertThat(message, containsString("can not be negative")); > catch (Exception anyOther) Assert.fail("Unexpected Exception"); > >
If perform method does not throw any exception Assert.fail(«Exception expected»); will be executed which fails the test.
If perform method throws exception of expected type, that is IllegalArgumentException here, then first catch block would be executed where we also can assert extra stuff.
If perform method throws exception of some other type then second catch would be executed where Assert.fail(«Unexpected Exception»); would fail the test.
- Code flow continues so we can make verifications after the method call.
- Can perform extra validations like expected message.
Using ExpectedException rule
JUnit comes with a handy rule to assert exceptions. We use it to assert exception type, message and some other details.
For this you need to use ExpectedException @Rule .
@Rule public ExpectedException expectedException = ExpectedException.none(); @Test public void performingNegativeNumberFailsWithExpectedExceptionRule() expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("can not be negative"); testMe.perform(-1); >
Here you make assertions about expected exception type and message before the method call which is expected to throw exception. If method throws right type of exception with right message content (checked with contains ), then the test passes otherwise fails.
- Code written after method call perform is not executed so it prevents writing verification if we have any.
Considering these options, I generally prefer to use ExpectedException rule unless I want explicit verifications to be done after the method call.
By explicit verifications, I mean verifying some method was in fact called with using Mockito.verify . Usually that case should be covered with non-exception cases, but sometimes cases where you have some code in exception handing that you want to assert that it was called, then you need to use the verbose try-cath way only.
So, are you going to start testing your sad exception cases or leave them neglected?
JUnit Fail Method
Here is a JUnit test that demonstrates JUnit fail method.
fail() throws an assertion error unconditionally. This might be helpful to mark an incomplete test (work in progress) or to ensure that an expected exception has been thrown
Source: (Foo.java)
public class Foo { void method(int x) throws IllegalArgumentException { if (x 0) { throw new IllegalArgumentException(); } } }
Unit Test Source: (UnitTest.java)
import static org.junit.Assert.fail; import org.junit.Before; import org.junit.Test; public class UnitTest { Foo foo; @Before public void setUp() { foo = new Foo(); } @Test public void failExampleTest() { if(foo == null){ fail("foo is null"); } try { foo.method(-1); fail("Should of thrown an IllegalArgumentException"); } catch (IllegalArgumentException e) { // handle exception } } }
Output:
$ mvn test ------------------------------------------------------- T E S T S ------------------------------------------------------- Running UnitTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.059 sec Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
Questions answered by this page:
JUnit fail example. Is there an actual use of fail in JUnit test case? Using the JUnit fail statement