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

  • 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 the correct Order was returned
Repository
async get(id: string): Promise<Order> {
    const order = await this.database.get(id);
    return order;
}
Unit Test
it('should return order successfully', async () => {
    const { databaseMock, repository } = systemUnderTest();
    const orderId = '1234567890';
    const customerId = 'c2afd554-25bc-4db7-90e6-fda488eb19ff';
    const order = {
        id: orderId,
        customerId,
    };
    when(databaseMock.get).calledWith(orderId).mockResolvedValue(order);

    const actual = await repository.get(orderId);

    expect(actual).toEqual(order);
});

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
async get(id: string): Promise<Order> {
    const order = await this.database.get(id);
    order.customerId = '3fcfab8b-0340-4af9-ac5b-6f06ced2607f';
    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
it('should return order successfully', async () => {
    const { databaseMock, repository } = systemUnderTest();
    const orderId = '1234567890';
    const customerId = 'c2afd554-25bc-4db7-90e6-fda488eb19ff';
    const order = {
        id: orderId,
        customerId,
    };
    when(databaseMock.get).calledWith(orderId).mockResolvedValue(order);

    const actual = await repository.get(orderId);

    const expected = {
        id: orderId,
        customerId,
    };
    expect(actual).toEqual(expected);
});
Expected
  • Create a new instance for expected objects
  • Do not change given or expected objects inside unit tests
  • Avoid writing mutable classes
  • Do not change object states after their creation, if a library contains mutable classes.

Full code samples can be found on GitHub.

August 20, 2024