How To Write Unit Tests - Expected Value

Poorly written unit tests can pass for faulty code. Improperly asserting the expected value can be one of the reasons behind the false positives.

This article explains how to properly set expected value in unit tests.

Scenario

  • Mutable Order class with setters
  • Repository class that returns an Order from a Database
  • Database interface is injected as a dependency to Repository and is of no further importance
  • A unit test that checks if a correct Order was returned
Order
public class Order {
    private String id;
    private String customerId;

    public void setCustomerId(String customerId) {
        this.customerId = customerId;
    }
}
Repository
public Order getOrderById(final String orderId) {
    return database.getOrderById(orderId);
}
Unit Test
@Test
void shouldSuccessfullyReturnOrderFromDatabase() {
    final var orderId = "1234567890";
    final var order = new Order(orderId, "someCustomer");
    when(database.getOrderById(orderId)).thenReturn(order);

    final var actual = repository.getOrderById(orderId);

    assertEquals(order, actual);
}

Expected Not Properly Set

A commit to the code base changed the state of the Order object. The unit test is still passing, and the code went to Production.

Repository
public Order getOrderById(final String orderId) {
    final var order = database.getOrderById(orderId);
    order.setCustomerId("someOtherCustomer");
    return order;
}
Given != Expected

Unit tests can pass when a given object instance is used as the expected object instance. That means that the expected object will contain all the changes applied to the given object.

Changes to the given object can happen inside a unit test, which is more obvious, or they can also happen inside the code being tested. The latter are difficult to spot.

Expected As a New Instance

The following unit test will not pass for faulty code.

Unit Test
@Test
void shouldSuccessfullyReturnOrderFromDatabase() {
    final var orderId = "1234567890";
    final var customerId = "someCustomer";
    final var order = new Order(orderId, customerId);
    when(database.getOrderById(orderId)).thenReturn(order);

    final var actual = repository.getOrderById(orderId);

    final var expected = new Order(orderId, customerId);
    assertEquals(expected, actual);
}

This example relies on having an overridden equals method that matches all instance fields. Without it, the unit test would fail, since it will try to match object references instead. The Order has been converted from class to record, which automatically generated the equals method.

Expected
  • Create a new instance for expected objects
  • Do not change given or expected objects inside unit tests
  • Do not write mutable classes
  • Do not change object states after their creation, if a library contains mutable classes.
  • The class being asserted should have the equals method overridden, which does not only check for reference equality

Full code samples can be found on GitHub.

August 20, 2024