Цепочки проверок с AssertJ
Проверок JUnit хватает для простых случаев, но для коллекций и объектов они становятся многословными. AssertJ — библиотека «текучих» (fluent) проверок: вы начинаете с assertThat(значение), и IDE подсказывает проверки, которые имеют смысл для этого типа. Тесты читаются почти как предложения, а сообщения о падении показывают всю картину. AssertJ работает внутри обычных тестов JUnit.
Подключение
Добавьте в dependencies файла pom.xml:
<!-- pom.xml -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.27.7</version>
<scope>test</scope>
</dependency>Все методы импортируются из одного класса: import static org.assertj.core.api.Assertions.*;.
Строки и числа
// src/test/java/com/example/blog/PostAssertJTest.java
@Test
void strings() {
String slug = Slugs.of("Hello, World!");
assertThat(slug)
.isEqualTo("hello-world")
.startsWith("hello")
.doesNotContain(" ");
}
@Test
void bigDecimals() {
BigDecimal total = new PriceCalculator().total(new BigDecimal("12.00"), 3);
assertThat(total).isEqualByComparingTo("36");
}Проверки соединяются в цепочку: каждая возвращает тот же объект проверки. isEqualByComparingTo сравнивает BigDecimal по значению, без учёта масштаба — 36 и 36.00 равны.
Коллекции
// src/test/java/com/example/blog/PostAssertJTest.java
@Test
void lists() {
InMemoryPostRepository repository = new InMemoryPostRepository();
repository.save(new Post(null, "Первый", "anna", true));
repository.save(new Post(null, "Второй", "oleg", false));
List<Post> posts = repository.findAll();
assertThat(posts)
.hasSize(2)
.extracting(Post::title)
.containsExactly("Первый", "Второй");
assertThat(posts)
.filteredOn(Post::published)
.extracting(Post::title, Post::author)
.containsExactly(tuple("Первый", "anna"));
}extracting превращает список постов в список заголовков, filteredOn оставляет только опубликованные. С несколькими извлекателями получаются значения tuple. Другие полезные проверки: contains, containsExactlyInAnyOrder, isEmpty, allMatch.
Исключения
// src/test/java/com/example/blog/PostAssertJTest.java
@Test
void exceptions() {
PostService service = new PostService(new InMemoryPostRepository(), (to, message) -> { });
assertThatThrownBy(() -> service.find(42))
.isInstanceOf(PostNotFoundException.class)
.hasMessage("Пост не найден: 42");
}Это вариант assertThrows из AssertJ вместе с проверкой сообщения в одной цепочке.
Сообщения о падении
Если бы в списке был "Второй", а тест ждал "Третий", AssertJ объяснил бы подробно:
org.opentest4j.AssertionFailedError:
Expecting actual:
["Первый", "Второй"]
to contain exactly (and in same order):
["Первый", "Третий"]
but some elements were not found:
["Третий"]
and others were not expected:
["Второй"]as() перед проверкой добавляет к сообщению метку — удобно, когда в тесте несколько похожих проверок:
assertThat(Slugs.of("Hello, World!")).as("слаг").startsWith("world");java.lang.AssertionError:
[слаг]
Expecting actual:
"hello-world"
to start with:
"world"Частая ошибка
assertThat сам по себе ничего не проверяет. Эта строка выглядит как проверка, но тест проходит при любом слаге:
assertThat(Slugs.of("Hello World").contains(" "));Результат contains типа boolean попадает в assertThat, и после него не вызван ни один метод проверки. Всегда завершайте цепочку: assertThat(slug).doesNotContain(" ") или assertThat(slug.contains(" ")).isFalse().