Unit Tests

A unit test is the smallest variant of creating an automated test. By default, Jmix automatically includes JUnit 5 as the test runner as well as Mockito for mocking of dependencies.

The term "unit test" is used to describe different concepts. Often it is also used to describe an automated testing in general. We will refer to a unit test as an automated test that verifies behaviour on a particular class / set of classes without dependencies (mainly Spring context and database).

Testing Isolated Functionality

In order to show the way how a unit test is created, let’s look at some example functionality: calculating a total for a given list order OrderLine instance that are associated to an Order.

For this there is a dedicated class OrderAmountCalculation which holds this calculation. This calculation class is not a Spring bean, but just a regular Java class.

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 The OrderAmountCalculation class is instantiated without the use of Spring through the constructor.
2 Entities are created by calling the constructor (without the usage of Jmix Metadata APIs).
3 Assertion of the calculation result is done via AssertJ assertions.

This test class does not contain any Spring Boot test annotations (like @SpringBootTest). This causes the Startup of the test to be much faster compared to a Spring Boot integration test.

But the lack of the Spring context in the test also means, that it is not possible use @Autowired in the test case to get instances of Jmix / Spring beans. In case the class of the production code, that is under test has any dependencies to other Spring beans, those dependencies have to be manually injected.

Mocking with Mockito

For Unit tests in particular this limitation is acceptable as the scope of the test case is normally an isolated functionality of one class.

Let’s assume the following example: we have a class that uses Jmix’s TimeSource API to get the current date. This is used to count the number of bookings that have been placed in this year for a given customer.

Here is the implementation of that class:

@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();
    }
}

The class is annotated with @Component to let Spring automatically inject the dependencies.

We want to test this functionality as a unit test. Therefore, we need to manually instantiate the RecentOrdersCounter class. For our concrete test case, we want to check the following:

Assuming we have two orders: one from 2019 and one from 2020, when the current year is 2020 we expect to have a count of one.

To achieve this, we need to control what TimeSource returns as now. This way we can simulate the fact that the current year is 2020.

Mockito is a mocking library that supports this kind of emulation. Jmix includes Mockito by default.

Here is an example on how this test case could look like:

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 The mock method creates an emulation instance that we can use to control the behaviour of the class.
2 We define that when the method now is called on the TimeSource, it should return 2020-03-01 as ZonedDateTime.
3 When instantiating the counter class, the mock / emulation instance of the TimeSource will be passed into the constructor.
When you want to test your Spring components in a unit test, it is best to use constructor based injection instead of @Autowired in the class. It has the main benefit that passing in the mocked instance via the constructor is possible in the unit test.

More information about the usage of Mockito can be found in their reference documentation.

Verify Behaviour with Assertions

Assertions are expressed through the AssertJ assertion library.

The AssertJ DSL provides a fluent API to perform validations on results of the classes under test. The assertion methods (like assertThat) should be statically imported from:

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

Here is a simple example of an AssertJ assertion for a String.

// given:
String customerName = "Mike Myers";

// expect:
assertThat(customerName)
        .startsWith("Mike")
        .endsWith("Myers");

Note that it is possible to chain multiple assertions that belong to the same result object.

In case of a failing test, JUnit / AssertJ will provide a proper error message about the difference between the expectation and the actual behaviour:

Expecting actual:
  "Mike Myers"
to end with:
  "Murphy"

Depending on the type of the object, AssertJ provides different assertion methods to compare values. When comparing lists, AssertJ provides corresponding assertion methods like hasSize or 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);

For more information about those assertions, you can read the AssertJ documentation.