Search

Dark theme | Light theme

September 30, 2024

Awesome AssertJ: Comparing Objects Recursively

To compare nested objects we can use the usingRecursiveComparison() method in AssertJ. We can set up the nested objects with values we expect, invoke a method that would return the actual nested objects, and then use the usingRecursiveComparison() method to compare the actual nested objects with the expected nested objects. This is a very clean way to compare nested objects. Also when we would add a new property to the nested objects our test would fail as we didn’t use that new property yet for our expected nested objects.

In the following example test we use the usingRecursiveComparison() method to compare actual nested objects with the expected nested objects. Our nested objects are of type Pirate and Ship.

// File: mrhaki/Ship.java
package mrhaki;

public record Ship(String name, String type, int crewSize) {}
// File: mrhaki/Pirate.java
package mrhaki;

public record Pirate(String name, String rank, Ship ship) {}

This is our class we want to test. The class PirateShipCreator creates the nested objects we want to write assertions for.

// File: mrhaki/PirateShipCreator.java
package mrhaki;

public class PirateShipCreator {
    public static Pirate createJackSparrow() {
        return new Pirate("Jack Sparrow", "Captain", createBlackPearl());
    }

    public static Pirate createDavyJones() {
        return new Pirate("Davy Jones", "Captain", createFlyingDutchmain());
    }

    private static Ship createBlackPearl() {
        return new Ship("Black Pearl", "Galleon", 100);
    }

    private static Ship createFlyingDutchmain() {
        return new Ship("Flying Dutchman", "Ghost ship", 199);
    }
}

The test class PirateShipCreatorTest uses the usingRecursiveComparison() method to compare actual nested objects with the expected nested objects:

// File: mrhaki/PirateShipCreatorTest.java
package mrhaki;

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

public class PirateShipCreatorTest {

    @Test
    public void testPirateEquality() {
        // given
        Ship expectedShip = new Ship("Black Pearl", "Galleon", 100);
        Pirate expectedPirate = new Pirate("Jack Sparrow", "Captain", expectedShip);

        // when
        Pirate actualPirate = PirateShipCreator.createJackSparrow();

        // then
        // assert equality using recursive comparison
        assertThat(actualPirate)
            .usingRecursiveComparison()
            .isEqualTo(expectedPirate);
    }
}

If we want to ignore a property we can use the ignoringFields(String…​) method. Or if we want to ignore properties of a certain type we can use the ignoringFieldsOfTypes(Class<?>…​) method. This can be very useful for properties that store dates we cannot setup properly in our tests.

Instead of ignoring fields we can also specify which fields we want to compare with the comparingOnlyFields(String…​) method. And there is a comparingOnlyFieldsOfTypes(Class<?>…​) method to specify which fields of a certain type we want to compare.

In the following tests we use all four methods to ignore or include fields in our comparison.

// File: mrhaki/PirateShipCreatorTest.java
package mrhaki;

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

public class PirateShipCreatorTest {

    @Test
    public void testPirateEqualityIgnoringShipCrewSize() {
        // given
        Ship expectedShip = new Ship("Flying Dutchman", "Ghost Ship", 100);
        Pirate expectedPirate = new Pirate("Davy Jones", "Captain", expectedShip);

        // when
        Pirate actualPirate = PirateShipCreator.createDavyJones();

        // then
        // assert equality using recursive comparison, ignoring crew size
        assertThat(actualPirate)
            .usingRecursiveComparison()
            .ignoringFields("ship.crewSize")
            .isEqualTo(expectedPirate);
    }

    @Test
    public void testPirateEqualityIgnoringIntegerFields() {
        // given
        Ship expectedShip = new Ship("Flying Dutchman", "Ghost Ship", 100);
        Pirate expectedPirate = new Pirate("Davy Jones", "Captain", expectedShip);

        // when
        Pirate actualPirate = PirateShipCreator.createDavyJones();

        // then
        // assert equality using recursive comparison, ignoring integer fields
        assertThat(actualPirate)
            .usingRecursiveComparison()
            .ignoringFieldsOfType(Integer.class)
            .isEqualTo(expectedPirate);
    }

    @Test
    public void testPirateEqualityComparingSelectedFields() {
        // given
        Ship expectedShip = new Ship("Flying Dutchman", "Ghost Ship", 100);
        Pirate expectedPirate = new Pirate("Davy Jones", "Captain", expectedShip);

        // when
        Pirate actualPirate = PirateShipCreator.createDavyJones();

        // then
        // assert equality using recursive comparison, comparing only selected fields
        assertThat(actualPirate)
            .usingRecursiveComparison()
            .comparingOnlyFields("name", "rank", "ship.name", "ship.type")
            .isEqualTo(expectedPirate);
    }

    @Test
    public void testPirateEqualityComparingSelectedTypeOfFields() {
        // given
        Ship expectedShip = new Ship("Flying Dutchman", "Ghost Ship", 100);
        Pirate expectedPirate = new Pirate("Davy Jones", "Captain", expectedShip);

        // when
        Pirate actualPirate = PirateShipCreator.createDavyJones();

        // then
        // assert equality using recursive comparison,
        // comparing only fields of type String and Ship
        assertThat(actualPirate)
            .usingRecursiveComparison()
            .comparingOnlyFieldsOfTypes(String.class, Ship.class)
            .isEqualTo(expectedPirate);
    }
}

Written with AssertJ 3.26.3.