Мягкие проверки и ещё проверки AssertJ
Обычная проверка — жёсткая: первая упавшая проверка выбрасывает исключение, и остаток теста не выполняется. Если у поста неверный и заголовок, и автор, вы узнаёте про заголовок, исправляете, запускаете снова — и только тогда видите автора. Мягкая проверка (soft assertion) тест не останавливает: она запоминает падение и идёт дальше, а в конце сообщает обо всех падениях сразу. В JUnit для этого есть assertAll; у AssertJ свой вариант с привычными цепочками assertThat.
Тесты урока лежат в src/test/java/com/example/blog/PostSoftAssertionsTest.java; AssertJ уже есть в pom.xml со времён урока о нём.
assertSoftly
// src/test/java/com/example/blog/PostSoftAssertionsTest.java
package com.example.blog;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
class PostSoftAssertionsTest {
private final InMemoryPostRepository repository = new InMemoryPostRepository();
private final PostService service = new PostService(repository, (to, message) -> { });
@Test
void publishKeepsFields() {
Post post = service.publish(service.create("Привет", "anna").id());
assertSoftly(softly -> {
softly.assertThat(post.id()).isEqualTo(1L);
softly.assertThat(post.title()).isEqualTo("Привет");
softly.assertThat(post.author()).isEqualTo("anna");
softly.assertThat(post.published()).isTrue();
});
}
}Внутри лямбды проверки идут через объект softly вместо статического assertThat. assertSoftly выполняет весь блок и только потом падает, если что-то было не так. Если бы тест ожидал заголовок "Здравствуйте" и автора "oleg", отчёт перечислил бы оба падения, каждое со своей строкой:
org.assertj.core.error.AssertJMultipleFailuresError:
Multiple Failures (2 failures)
-- failure 1 --
expected: "Здравствуйте"
but was: "Привет"
at SoftFailTest.lambda$publishKeepsFields$0(SoftFailTest.java:18)
-- failure 2 --
expected: "oleg"
but was: "anna"
at SoftFailTest.lambda$publishKeepsFields$0(SoftFailTest.java:19)Есть и форма без лямбды — создайте объект сами и вызовите assertAll() в конце:
// src/test/java/com/example/blog/PostSoftAssertionsTest.java
@Test
void softAssertionsObject() {
Post post = service.create("Привет", "anna");
SoftAssertions softly = new SoftAssertions();
softly.assertThat(post.title()).startsWith("При");
softly.assertThat(post.published()).isFalse();
softly.assertAll();
}assertSoftly или assertAll
assertAll из JUnit | assertSoftly из AssertJ | |
|---|---|---|
| Проверки внутри | Отдельные лямбды, по одной на проверку | Обычные строки, сколько угодно |
| Какие проверки | assertEquals, assertTrue и другие | Все цепочки AssertJ |
| Нужна библиотека | Нет | AssertJ |
Если в проекте уже есть AssertJ, assertSoftly читается лучше; в тестах на чистом JUnit хватит assertAll. Мягкие проверки нужны для нескольких полей одного результата, а не чтобы склеить в один тест несвязанные проверки.
Группа проверок одного объекта: satisfies
// src/test/java/com/example/blog/PostSoftAssertionsTest.java
@Test
void satisfies() {
Post post = service.create(" Привет ", "anna");
assertThat(post).satisfies(p -> {
assertThat(p.title()).isEqualTo("Привет");
assertThat(p.published()).isFalse();
});
}
@Test
void allSatisfy() {
service.create("Первый", "anna");
service.create("Второй", "oleg");
List<Post> posts = repository.findAll();
assertThat(posts).allSatisfy(p -> assertThat(p.published()).isFalse());
assertThat(posts).anySatisfy(p -> assertThat(p.author()).isEqualTo("oleg"));
}satisfies выполняет блок проверок над значением — удобно посреди цепочки. allSatisfy проверяет каждый элемент списка, anySatisfy — что хотя бы один элемент проходит проверку. Оба теста проходят. (containsExactly, extracting и filteredOn были в уроке об AssertJ.)
Сравнение по полям: usingRecursiveComparison
id выдаёт репозиторий, поэтому isEqualTo(new Post(...)) напрямую не подходит. Рекурсивное сравнение проверяет поля по одному и умеет пропускать некоторые:
// src/test/java/com/example/blog/PostSoftAssertionsTest.java
@Test
void recursiveComparison() {
Post post = service.create("Привет", "anna");
assertThat(post)
.usingRecursiveComparison()
.ignoringFields("id")
.isEqualTo(new Post(null, "Привет", "anna", false));
}При падении оно перечисляет все различающиеся поля (сокращено):
Expecting actual:
Post[id=1, title=Привет, author=anna, published=false]
to be equal to:
Post[id=null, title=Привет, author=oleg, published=true]
when recursively comparing field by field, but found the following 2 differences:
field/property 'author' differ:
- actual value : "anna"
- expected value: "oleg"
...
field/property 'published' differ:
- actual value : false
- expected value: trueОно работает и для объектов разных классов с одинаковыми именами полей — например, поста и DTO.
Частая ошибка
В форме с new SoftAssertions() забыт assertAll() в конце:
SoftAssertions softly = new SoftAssertions();
softly.assertThat(post.title()).isEqualTo("Здравствуйте");
softly.assertThat(post.published()).isTrue();Обе проверки неверны, но тест зелёный:
[INFO] Running com.example.blog.NoAssertAllTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.961 s -- in com.example.blog.NoAssertAllTestМягкие проверки только собирают падения, а сообщает о них assertAll(). Предпочитайте assertSoftly(softly -> ...) — он вызывает assertAll() сам.