Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Мягкие проверки и ещё проверки 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 из JUnitassertSoftly из 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() сам.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.