Юнит-тесты
Юнит-тест представляет собой наиболее узконаправленный вариант автоматизированного теста.
Термин "юнит-тест" используется для описания различных концепций, в том числе для обозначения автоматизированного тестирования в целом. Мы будем относиться к юнит-тесту как к автоматизированному тесту, проверяющему поведение определенного класса или набора классов без зависимостей (в первую очередь без контекста Spring и базы данных).
Тестирование изолированной функциональности
Для демонстрации процесса создания юнит-теста рассмотрим функциональность вычисления общей суммы для списка экземпляров OrderLine, связанных с Order.
Это вычисление выполняет отдельный класс под названием OrderAmountCalculation. Это не бин Spring, а обычный класс Java:
public class OrderAmountCalculation {
    public BigDecimal calculateTotalAmount(List<OrderLine> orderLines) {
        return orderLines.stream()
                .map(this::totalPriceForOrderLine)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    private BigDecimal totalPriceForOrderLine(OrderLine orderLine) {
        BigDecimal productPrice = orderLine.getProduct().getPrice();
        BigDecimal quantity = BigDecimal.valueOf(orderLine.getQuantity());
        return productPrice.multiply(quantity);
    }
}Пример юнит-теста для этой функциональности:
class OrderAmountCalculationTest {
    private OrderAmountCalculation orderAmountCalculation;
    private static final BigDecimal USD499 = BigDecimal.valueOf(499.0);
    private Product iPad;
    @Test
    void calculateTotalAmount() {
        // given:
        orderAmountCalculation = new OrderAmountCalculation(); (1)
        // and:
        iPad = new Product(); (2)
        iPad.setName("Apple iPad");
        iPad.setPrice(USD499);
        // and:
        OrderLine twoIpads = new OrderLine();
        twoIpads.setProduct(iPad);
        twoIpads.setQuantity(2.0);
        // when:
        var totalAmount = orderAmountCalculation.calculateTotalAmount(
                List.of(twoIpads)
        );
        // then:
        assertThat(totalAmount) (3)
                .isEqualByComparingTo(BigDecimal.valueOf(998.0));
    }
}| 1 | Класс OrderAmountCalculationсоздается через конструктор без использования Spring. | 
| 2 | Сущности создаются путем вызова конструктора (без использования API Jmix Metadata). | 
| 3 | Проверка результата вычислений выполняется с использованием утверждений AssertJ. | 
Данный класс теста не содержит аннотаций тестов Spring Boot (например, @SpringBootTest), поэтому тест не использует контекст Spring и, следовательно, выполняется очень быстро. Однако отсутствие контекста Spring в тесте также означает, что невозможно использовать @Autowired в классе теста для получения экземпляров бинов Spring. Если у тестируемого класса есть зависимости от бинов Spring, эти зависимости должны быть созданы вручную.
Создание фиктивных объектов с Mockito
В случае юнит-тестов указанное выше ограничение является приемлемым, поскольку обычно тестирование ограничивается изолированной функциональностью отдельного класса.
Рассмотрим следующий пример: есть класс, который вызывает API Jmix TimeSource для получения текущей даты. Он используется для подсчета количества бронирований, размещенных в текущем году для определенного клиента.
Вот реализация этого класса:
@Component
public class RecentOrdersCounter {
    private final TimeSource timeSource;
    public RecentOrdersCounter(TimeSource timeSource) {
        this.timeSource = timeSource;
    }
    public long countFromThisYear(Customer customer) {
        return customer.getOrders().stream()
                .filter(this::fromThisYear)
                .count();
    }
    private boolean fromThisYear(Order order) {
        int thisYear = timeSource.now().toLocalDate().getYear();
        return thisYear == order.getDate().getYear();
    }
}Класс аннотирован как @Component, чтобы Spring автоматически создавал его и внедрял зависимости. Но если вы хотите протестировать эту функциональность в юнит-тесте, вам нужно вручную создать экземпляр класса RecentOrdersCounter и предоставить ему экземпляр TimeSource через конструктор.
Для тестирования функциональности RecentOrdersCounter имеет смысл проверить следующее:
Предположим, у нас есть два заказа: один из 2019 года и один из 2020 года. Когда текущий год - 2020, мы ожидаем получить счетчик равным единице.
Для достижения этого необходимо управлять тем, что возвращает TimeSource как текущее время, в частности, эмулировать тот факт, что текущий год - 2020.
Такую эмуляцию можно реализовать с помощью Mockito - библиотеки для создания фиктивных объектов (моков). Она доступна в проектах Jmix по умолчанию.
Вот пример того, как может выглядеть тест:
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class RecentOrdersCounterTest {
    TimeSource timeSourceMock = mock(TimeSource.class); (1)
    LocalDateTime MAR_01_2020 = LocalDate.of(2020, 3, 1).atStartOfDay();
    @Test
    void given_itIs2020_and_customerWithOneOrderIn2020_when_countFromThisYear_then_resultIs1() {
        // given:
        when(timeSourceMock.now())
                .thenReturn(ZonedDateTime.of(MAR_01_2020, ZoneId.systemDefault()));  (2)
        // and:
        RecentOrdersCounter counter = new RecentOrdersCounter(timeSourceMock); (3)
        // and:
        Customer customer = new Customer();
        Order orderFrom2020 = orderWithDate(LocalDate.of(2020, 2, 5));
        Order orderFrom2019 = orderWithDate(LocalDate.of(2019, 5, 1));
        customer.setOrders(List.of(orderFrom2020, orderFrom2019));
        // when:
        long recentOrdersCount = counter.countFromThisYear(customer);
        // then:
        assertThat(recentOrdersCount)
                .isEqualTo(1);
    }| 1 | Метод Mockito.mock()создает фиктивный экземпляр, который можно использовать для управления поведением класса. | 
| 2 | Вызов Mockito.when()определяет, что при вызове методаnow()наTimeSourceон должен возвращать2020-03-01в видеZonedDateTime. | 
| 3 | При создании экземпляра класса счетчика в конструктор передается мок (фиктивный экземпляр) TimeSource. | 
| Если вы собираетесь тестировать ваши Spring-компоненты в юнит-тестах, используйте инжекцию зависимостей через конструктор вместо @Autowiredна полях класса. | 
Более подробную информацию о использовании Mockito можно найти в его документации.
Проверка поведения с использованием утверждений
Утверждения могут быть выражены с использованием библиотеки AssertJ.
DSL AssertJ предоставляет fluent API для выполнения проверок результатов тестируемых классов. Методы утверждений (например, assertThat) должны быть статически импортированы из org.assertj.core.api.Assertions, например:
import static org.assertj.core.api.Assertions.assertThat;Вот простой пример утверждения AssertJ для строки:
// given:
String customerName = "Mike Myers";
// expect:
assertThat(customerName)
        .startsWith("Mike")
        .endsWith("Myers");Обратите внимание, что можно объединить несколько утверждений, принадлежащих одному объекту результата.
В случае неудачного теста JUnit / AssertJ предоставит правильное сообщение об ошибке с разницей между ожидаемым и фактическим поведением:
Expecting actual: "Mike Myers" to end with: "Murphy"
В зависимости от типа объекта AssertJ предоставляет различные методы утверждений для сравнения значений. Например, при сравнении списков AssertJ предоставляет методы  hasSize и contains:
// given:
String bruceWillis = "Bruce Willis";
String mikeMyers = "Mike Myers";
String eddiMurphy = "Eddi Murphy";
// when:
List<String> customers = List.of(mikeMyers, eddiMurphy);
// expect:
assertThat(customers)
        .hasSize(2)
        .contains(eddiMurphy)
        .doesNotContain(bruceWillis);Дополнительную информацию о методах утверждений см. в документации AssertJ.