How To Write Unit Tests - Asserting Collection Equality

A "collection" is a type that can hold a group of elements, like array, map, set etc. Some of these types are mutable and resizable. Unit tests that cover them should take that into account.

This article explains how to properly assert array equality. The same principles applies to other types of collections.

Scenario

  • Repository class returns an array 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.
Repository
async getAll(): Promise<Order[]> {
    const orders = await this.database.getAll();
    return orders;
}

Asserting Equality of the Collection Size Is Not Enough

The following unit test checks if the returned array length (size) is as expected. The unit test passes.

Unit Test
it('should get all orders from the database - collection size', async () => {
    const { databaseMock, repository } = systemUnderTest();
    const orderId = '1234567890';
    const order = { id: orderId };
    const orders = [order];
    when(databaseMock.getAll).calledWith().mockResolvedValue(orders);

    const actual = await repository.getAll();

    expect(actual.length).toEqual(1);
});

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

Repository
async getAll(): Promise<Order[]> {
    const orders = await this.database.getAll();
    orders[0] = { id: '9999999999' };
    return orders;
}

Asserting Equality of a Collection Element Is Not Enough

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

Unit Test
it('should get all orders from the database - single collection element', async () => {
    const { databaseMock, repository } = systemUnderTest();
    const orderId = '1234567890';
    const order = { id: orderId };
    const orders = [order];
    when(databaseMock.getAll).calledWith().mockResolvedValue(orders);

    const actual = await repository.getAll();

    expect(actual[0].id).toEqual(orderId);
});

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

Repository
async getAll(): Promise<Order[]> {
    const orders = await this.database.getAll();
    orders.push({ id: '8888888888' });
    return orders;
}

Expected Collection Same as Given Collection

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
it('should get all orders from the database - expected same as given', async () => {
    const { databaseMock, repository } = systemUnderTest();
    const orderId = '1234567890';
    const order = { id: orderId };
    const orders = [order];
    when(databaseMock.getAll).calledWith().mockResolvedValue(orders);

    const actual = await repository.getAll();

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

The same unit test passes for the code that changes properties of an element in the array.

Repository
async getAll(): Promise<Order[]> {
    const orders = await this.database.getAll();
    orders[0].id = '7777777777';
    return orders;
}

Asserting Collections Are Deeply Equal

The following unit test will not pass for faulty code.

Unit Test
it('should get all orders from the database', async () => {
    const { databaseMock, repository } = systemUnderTest();
    const orderId = '1234567890';
    const order = { id: orderId };
    const orders = [order];
    when(databaseMock.getAll).calledWith().mockResolvedValue(orders);

    const actual = await repository.getAll();

    const expectedOrder = { id: orderId };
    const expected = [expectedOrder];
    expect(actual).toEqual(expected);
});

This example deeply compares two different array instances.

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

Full code samples can be found on GitHub.

September 5, 2024