Encodera

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

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

Оглавление

Цепочки проверок с 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().

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

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