How To Write Unit Tests - Asserting List Equality

There are multiple implementations of lists, some of which are mutable and resizable. Unit tests that cover lists should take all implementations into account.

This article explains how to properly assert list equality.

Scenario

  • Order record
  • Repository class returns a list of Order instances from a Database
  • Database interface is injected as a dependency to Repository and is of no further importance
  • A unit test that asserts if the returned value is as expected.
Order
public record Order(String id) {
}
Repository
public List<Order> getOrders() {
    return database.getOrders();
}

Asserting Equality of the List Size Is Not Enough

The following unit test checks if the returned list size is as expected. The unit test passes.

Unit Test
@Test
void shouldReturnListOfSizeOne() {
    final var order = new Order("1234567890");
    // mutable, but fixed size
    final var orders = Arrays.asList(order);
    when(database.getOrders()).thenReturn(orders);

    final var actual = repository.getOrders();

    assertEquals(1, actual.size());
}

The same unit test passes for the code that changes the list elements, but does not change the list size.

Repository
public List<Order> getOrders() {
    final var orders = database.getOrders();
    orders.set(0, new Order("9999999999"));
    return orders;
}

Asserting Equality of a List Element Is Not Enough

The following unit test checks if the first element of the returned list is as expected. The unit test passes.

Unit Test
@Test
void shouldReturnOrderWithId() {
    final var orderId = "1234567890";
    final var order = new Order(orderId);
    // mutable and resizable
    final var orders = new ArrayList<Order>() {
        {
            add(order);
        }
    };
    when(database.getOrders()).thenReturn(orders);

    final var actual = repository.getOrders();

    assertEquals(orderId, actual.get(0).id());
}

The same unit test passes for the code that adds another element to the list.

Repository
public List<Order> getOrders() {
    final var orders = database.getOrders();
    orders.add(new Order("9999999999"));
    return orders;
}

Expected List Same as Given List

As explained in another blog post about expected values, comparing a given value to the actual value can also lead to false positives.

Unit Test
assertEquals(orders, actual);

Asserting Lists Are Deeply Equal

The following unit test will not pass for faulty code.

Unit Test
@Test
void shouldSuccessfullyReturnOrders() {
    final var orderId = "1234567890";
    final var order = new Order(orderId);
    when(database.getOrders()).thenReturn(List.of(order));

    final var actual = repository.getOrders();

    final var expected = List.of(new Order(orderId));
    assertEquals(expected, actual);
}

This example compares two list instances. Mockito assertEquals method asserts that lists are deeply equal.

Asserting List Equality
  • Create a new instance of the expected list object
  • Create a new instance of all objects in the expected list
  • Assert that lists are "deeply" equal (all elements, order of elements and size are equal)

Full code samples can be found on GitHub.

September 5, 2024