BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News EqualsVerifier Delivers Improved Support for JPA Entities

EqualsVerifier Delivers Improved Support for JPA Entities

The EqualsVerifier library may be used in Java unit tests to automatically verify equals() implementations and provides one hundred percent code coverage on equals() and hashCode() methods. Recent releases have improved support for the Java Persistence Architecture and Jakarta Persistence specification (JPA), by requiring the use of getters instead of fields, and solving several related bugs.

Version 3.15 introduced a breaking change as it requires using getters inside the equals() method for all mapped fields in JPA entities such as @OneToMany and @ManyToOne. The requirement is independent of using the FetchType.LAZY or FetchType.EAGER strategies, as in some cases even eager fields may be null. The new behavior can be disabled with suppress(Warning.JPA_GETTER) in case it's not desired. More details may be found in the JPA entities chapter of the manual.

Version 3.15.1 resolved an error which was thrown when the getter method of an unused mapped field in a JPA entity wasn’t used in the equals() and hashCode() methods. Version 3.15.2 resolved an error which was thrown when the hashCode() method didn’t use all fields of a JPA entity, despite suppressing Warning.STRICT_HASHCODE.

The 3.15 release also introduced the new withFieldnameToGetterConverter() method which allows a custom derivation of getter names from field names, in case they differ.

EqualsVerifier may be used after adding the following Maven dependency:

<dependency>
    <groupId>nl.jqno.equalsverifier</groupId>
    <artifactId>equalsverifier</artifactId>
    <version>3.15.2</version>
    <scope>test</scope>
</dependency>

Alternatively, a fat jar without transitive dependencies may be used:

<dependency>
    <groupId>nl.jqno.equalsverifier</groupId>
    <artifactId>equalsverifier-nodep</artifactId>
    <version>3.15.2</version>
    <scope>test</scope>
</dependency>

The following Student class provides an equals() method:

class Student {
    private String firstName;
    private String lastName;

    public Student(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Student student)) {
            return false;
        }
        return firstName == student.firstName && 
            lastName == student.firstName;
    }
}

EqualsVerifier may be used in a JUnit test in order to verify the implementation of the equals() method in the Student class:

private String expectedErrorMessage = """
    	EqualsVerifier found a problem in class 
          com.example.EqualsVerifierTest$Student.
    	-> hashCode: hashCodes should be equal:""";

@Test
public void testStudentEquals() {
    AssertionError assertionError =
            Assertions.assertThrows(AssertionError.class, () -> {
        EqualsVerifier.forClass(Student.class).verify();
    });

    assertTrue(assertionError.getMessage()
        .startsWith(expectedErrorMessage));
}

An assertion is thrown as the equals() method isn't correct.

The issues may be resolved by adding the final keyword to the firstName and lastName fields and the equals() method. After that, correct implementations for the equals() and hashcode() methods should be provided:

class FixedStudent {
    private final String firstName;
    private final String lastName;

    public FixedStudent(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public final boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof FixedStudent)) return false;
        FixedStudent that = (FixedStudent) o;
        return Objects.equals(firstName, that.firstName) &&
            Objects.equals(lastName, that.lastName);
    }

    @Override
    public final int hashCode() {
        return Objects.hash(firstName, lastName);
    }
}

EqualsVerifier can now be used to verify that the equals() method of the FixedStudent class is correct:

@Test
public void testFixedStudentEquals() {
    Assertions.assertDoesNotThrow(() -> {
        EqualsVerifier.forClass(FixedStudent.class).verify();
    });
}

Jan Ouwens, senior software developer at Yoink! created EqualsVerifier and maintains a website with more information, including a detailed manual. Tom Cools, software developer at Info Support, created a short introduction video to teach the basics of EqualsVerifier. The GitHub changelog lists all the recent releases and their respective changes.

About the Author

Rate this Article

Adoption
Style

BT