AssertJ для Java: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

AssertJ для Java: Полное руководство по Fluent Assertions

1. Что это и зачем нужна

AssertJ — это мощная библиотека fluent-assertions (текучих утверждений) для языка Java, предназначенная для написания читаемых, выразительных и самодокументируемых тестов. В отличие от стандартного JUnit или TestNG, которые предоставляют лишь базовые методы вроде assertEquals(a, b), AssertJ предлагает богатый API с цепочками вызовов, что делает код тестов похожим на естественный язык. Например, вместо assertTrue(list.contains("foo")) вы пишете assertThat(list).contains("foo").

Зачем она нужна? Во-первых, AssertJ значительно улучшает читаемость тестов. Когда разработчик видит assertThat(result).isNotNull().hasSize(5).contains("expected"), он сразу понимает, что проверяется: результат не null, размер равен 5, и он содержит строку "expected". Во-вторых, AssertJ предоставляет детальные сообщения об ошибках. Вместо скучного "expected true but was false" вы получите "Expected list to contain 'foo' but could not find it in [bar, baz]". В-третьих, библиотека поддерживает множество типов данных: коллекции, строки, числа, даты, файлы, исключения и даже пользовательские объекты.

AssertJ особенно полезна в проектах, где важна поддержка тестовой базы. Благодаря fluent API, тесты становятся менее хрупкими при рефакторинге, так как цепочки методов легко модифицировать. Библиотека активно развивается, имеет отличную документацию и широко используется в индустрии (Spring, Hibernate, Netflix и др.).

2. Установка

AssertJ распространяется через Maven Central. Для использования в проекте добавьте зависимость в ваш pom.xml (Maven) или build.gradle (Gradle).

Maven

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.25.3</version> <!-- Проверьте актуальную версию на Maven Central -->
    <scope>test</scope>
</dependency>

Gradle

testImplementation 'org.assertj:assertj-core:3.25.3'

Если вы используете JUnit 5, дополнительных зависимостей не требуется. Для JUnit 4 AssertJ также работает без проблем. Убедитесь, что версия AssertJ совместима с вашей версией Java (3.x требует Java 8 или выше).

3. Быстрый старт — минимальный рабочий пример

После добавления зависимости вы можете сразу начать использовать AssertJ. Основной метод — assertThat(), который принимает объект и возвращает соответствующий assertion-объект.

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

public class QuickStartTest {

    @Test
    void testSimpleAssertion() {
        String name = "AssertJ";
        
        // Базовое утверждение
        assertThat(name)
            .isNotNull()
            .isNotEmpty()
            .startsWith("Ass")
            .endsWith("J")
            .hasSize(7);
        
        // Проверка чисел
        int number = 42;
        assertThat(number)
            .isPositive()
            .isGreaterThan(40)
            .isLessThan(50);
        
        // Проверка коллекций
        var list = java.util.List.of("a", "b", "c");
        assertThat(list)
            .hasSize(3)
            .contains("a", "b")
            .doesNotContain("z");
    }
}

Этот пример демонстрирует ключевую идею: цепочка методов, каждый из которых возвращает тот же assertion-объект, позволяя писать последовательные проверки. Если любое утверждение не выполняется, тест падает с понятным сообщением.

4. Основные методы/функции/классы

AssertJ предоставляет сотни методов. Ниже приведены 8 наиболее часто используемых с сигнатурами, описанием и примерами.

4.1. isEqualTo

Сигнатура: AbstractAssert isEqualTo(Object expected)

Что делает: Проверяет, что фактическое значение равно ожидаемому (через equals()). Для массивов и коллекций сравнивает содержимое.

@Test
void testIsEqualTo() {
    String actual = "Hello";
    assertThat(actual).isEqualTo("Hello");
    
    int[] numbers = {1, 2, 3};
    assertThat(numbers).isEqualTo(new int[]{1, 2, 3}); // Работает для массивов
}

4.2. containsExactly

Сигнатура: AbstractIterableAssert containsExactly(E... values)

Что делает: Проверяет, что коллекция содержит ровно указанные элементы в том же порядке. Идеально для проверки порядка элементов.

@Test
void testContainsExactly() {
    var list = java.util.List.of("apple", "banana", "cherry");
    assertThat(list).containsExactly("apple", "banana", "cherry");
    
    // Если порядок не важен, используйте containsExactlyInAnyOrder
    assertThat(list).containsExactlyInAnyOrder("cherry", "apple", "banana");
}

4.3. hasSize

Сигнатура: AbstractIterableAssert hasSize(int expected)

Что делает: Проверяет размер коллекции, строки, массива или Map.

@Test
void testHasSize() {
    var list = java.util.List.of(1, 2, 3);
    assertThat(list).hasSize(3);
    
    String text = "AssertJ";
    assertThat(text).hasSize(7);
    
    var map = java.util.Map.of("key1", "value1", "key2", "value2");
    assertThat(map).hasSize(2);
}

4.4. extracting

Сигнатура: AbstractListAssert extracting(Function<T, Object> extractor)

Что делает: Извлекает поле или результат метода из каждого элемента коллекции и позволяет проверять извлеченные значения. Мощный инструмент для работы с POJO.

@Test
void testExtracting() {
    record Person(String name, int age) {}
    
    var people = java.util.List.of(
        new Person("Alice", 30),
        new Person("Bob", 25)
    );
    
    assertThat(people)
        .extracting(Person::name)
        .contains("Alice", "Bob");
    
    // Можно извлекать несколько полей
    assertThat(people)
        .extracting(Person::name, Person::age)
        .contains(
            tuple("Alice", 30),
            tuple("Bob", 25)
        );
}

4.5. filteredOn

Сигнатура: AbstractListAssert filteredOn(Predicate<T> predicate)

Что делает: Фильтрует коллекцию по условию и возвращает новый assertion для отфильтрованных элементов. Позволяет проверять подмножества.

@Test
void testFilteredOn() {
    record Product(String name, double price) {}
    
    var products = java.util.List.of(
        new Product("Laptop", 1200.0),
        new Product("Mouse", 25.0),
        new Product("Keyboard", 80.0)
    );
    
    assertThat(products)
        .filteredOn(p -> p.price() > 100)
        .extracting(Product::name)
        .containsExactly("Laptop");
}

4.6. isInstanceOf

Сигнатура: AbstractAssert isInstanceOf(Class<?> type)

Что делает: Проверяет, что объект является экземпляром указанного класса (или его подкласса). Полезно при работе с полиморфизмом.

@Test
void testIsInstanceOf() {
    Object value = "Hello";
    assertThat(value).isInstanceOf(String.class);
    
    // Для точного совпадения класса используйте isExactlyInstanceOf
    assertThat(value).isExactlyInstanceOf(String.class);
    
    // Проверка интерфейсов
    assertThat(value).isInstanceOf(CharSequence.class);
}

4.7. matches

Сигнатура: AbstractStringAssert matches(Pattern pattern) или matches(String regex)

Что делает: Проверяет, что строка соответствует регулярному выражению. Также есть doesNotMatch для обратной проверки.

@Test
void testMatches() {
    String email = "user@example.com";
    assertThat(email).matches("^[A-Za-z0-9+_.-]+@(.+)$");
    
    // С использованием Pattern
    var pattern = java.util.regex.Pattern.compile("\\d{3}-\\d{2}-\\d{4}");
    assertThat("123-45-6789").matches(pattern);
}

4.8. isCloseTo (для чисел с плавающей точкой)

Сигнатура: AbstractDoubleAssert isCloseTo(double expected, Offset offset) или isCloseTo(double expected, Percentage percentage)

Что делает: Проверяет, что число с плавающей точкой находится в пределах заданного смещения или процента от ожидаемого значения. Решает проблему сравнения double/float.

@Test
void testIsCloseTo() {
    double actual = 3.14159;
    assertThat(actual).isCloseTo(3.14, within(0.01)); // Offset.of(0.01)
    assertThat(actual).isCloseTo(3.14, withinPercentage(1.0)); // 1% tolerance
    
    // Для float
    float f = 0.1f + 0.2f;
    assertThat(f).isCloseTo(0.3f, within(0.0001f));
}

Примечание: within и withinPercentage импортируются из org.assertj.core.api.Assertions.within и withinPercentage.

5. Реальный практический пример (100-200 строк кода)

Рассмотрим тестирование сервиса заказов интернет-магазина. У нас есть класс OrderService, который обрабатывает заказы, и мы хотим написать комплексные тесты с использованием AssertJ.

import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.tuple;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;

// Модельные классы
record OrderItem(String productId, String name, int quantity, BigDecimal price) {}
record Order(String id, String customerEmail, List<OrderItem> items, 
             LocalDateTime createdAt, OrderStatus status) {}
enum OrderStatus { PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED }

// Сервис, который мы тестируем
class OrderService {
    private final Map<String, Order> orders = new HashMap<>();
    
    public Order createOrder(String customerEmail, List<OrderItem> items) {
        String id = UUID.randomUUID().toString();
        Order order = new Order(id, customerEmail, items, LocalDateTime.now(), OrderStatus.PENDING);
        orders.put(id, order);
        return order;
    }
    
    public Order confirmOrder(String orderId) {
        Order order = orders.get(orderId);
        if (order == null) throw new IllegalArgumentException("Order not found");
        Order confirmed = new Order(order.id(), order.customerEmail(), order.items(), 
                                     order.createdAt(), OrderStatus.CONFIRMED);
        orders.put(orderId, confirmed);
        return confirmed;
    }
    
    public List<Order> findOrdersByCustomer(String email) {
        return orders.values().stream()
            .filter(o -> o.customerEmail().equals(email))
            .toList();
    }
    
    public BigDecimal calculateTotal(String orderId) {
        Order order = orders.get(orderId);
        if (order == null) throw new IllegalArgumentException("Order not found");
        return order.items().stream()
            .map(item -> item.price().multiply(BigDecimal.valueOf(item.quantity())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

// Тесты
class OrderServiceTest {
    
    private OrderService orderService;
    private List<OrderItem> sampleItems;
    
    @BeforeEach
    void setUp() {
        orderService = new OrderService();
        sampleItems = List.of(
            new OrderItem("P001", "Laptop", 1, new BigDecimal("1200.00")),
            new OrderItem("P002", "Mouse", 2, new BigDecimal("25.00")),
            new OrderItem("P003", "Keyboard", 1, new BigDecimal("80.00"))
        );
    }
    
    @Test
    void testCreateOrder() {
        Order order = orderService.createOrder("customer@test.com", sampleItems);
        
        // Проверяем, что заказ создан корректно
        assertThat(order)
            .isNotNull()
            .satisfies(o -> {
                assertThat(o.id()).isNotEmpty();
                assertThat(o.customerEmail()).isEqualTo("customer@test.com");
                assertThat(o.status()).isEqualTo(OrderStatus.PENDING);
                assertThat(o.createdAt()).isBeforeOrEqualTo(LocalDateTime.now());
            });
        
        // Проверяем элементы заказа
        assertThat(order.items())
            .hasSize(3)
            .extracting(OrderItem::productId, OrderItem::name, OrderItem::quantity)
            .containsExactly(
                tuple("P001", "Laptop", 1),
                tuple("P002", "Mouse", 2),
                tuple("P003", "Keyboard", 1)
            );
    }
    
    @Test
    void testConfirmOrder() {
        Order created = orderService.createOrder("customer@test.com", sampleItems);
        Order confirmed = orderService.confirmOrder(created.id());
        
        // Проверяем, что статус изменился
        assertThat(confirmed)
            .isNotNull()
            .hasFieldOrPropertyWithValue("status", OrderStatus.CONFIRMED)
            .hasFieldOrPropertyWithValue("customerEmail", "customer@test.com");
        
        // Проверяем, что оригинальный заказ не изменился (иммутабельность)
        assertThat(created.status()).isEqualTo(OrderStatus.PENDING);
    }
    
    @Test
    void testFindOrdersByCustomer() {
        orderService.createOrder("alice@test.com", sampleItems);
        orderService.createOrder("bob@test.com", List.of(
            new OrderItem("P                    

Рекомендаций