All Downloads are FREE. Search and download functionalities are using the official Maven repository.

name.didier.david.test4j.assertions.EqualsVerifier Maven / Gradle / Ivy

package name.didier.david.test4j.assertions;

import static java.util.Arrays.asList;

import static org.assertj.core.api.Assertions.assertThat;

import static com.google.common.collect.Lists.newArrayList;

import static name.didier.david.check4j.ConciseCheckers.checkNotNull;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Asserts that a class correctly implements {@link Object#equals(Object)} and {@link Object#hashCode()}.
 *
 * 

* See Effective Java, Second Edition by Joshua Bloch: *

* *
    *
  • Item 8: Obey the general contract when overriding equals
  • *
  • Item 9: Always override hashCode when you override equals
  • *
* *

* Usage: *

* *
 * class MyObject {
 *
 *     public MyObject(int a, int b) {
 *         super();
 *         this.a = a;
 *         this.b = b;
 *     }
 *
 *     // use a and b for equals()
 *     // use a and b for hashCode()
 * }
 *
 * EqualsVerifier myObject = new EqualsVerifier(new MyObject(1, 2));
 * myObject.mustBeEqualsTo(new MyObject(1, 2));
 * myObject.mustNotBeEqualsTo(new Object());
 * myObject.mustNotBeEqualsTo(new MyObject(9, 2));
 * myObject.mustNotBeEqualsTo(new MyObject(1, 9));
 * myObject.mustNotBeEqualsTo(new MyObject(9, 9));
 * myObject.verify();
 * 
* * @author ddidier */ public class EqualsVerifier { /** The associated logger. */ private static final Logger logger = LoggerFactory.getLogger(EqualsVerifier.class); /** The tested object. */ private final Object object; /** Objects that are known to be equals to {@code #object}. */ private final List equalsObjects = newArrayList(); /** Objects that are known to not be equals to {@code #object}. */ private final List unequalsObjects = newArrayList(); /** * Default constructor. * * @param object the object to be verified. */ public EqualsVerifier(final Object object) { super(); this.object = checkNotNull(object, "object"); } /** * Adds objects that are known to be equals to {@code #object} to test against. * * @param equalObject an object that is known to be equals to {@code #object}. * @param equalObjects objects that are known to be equals to {@code #object}. * @return {@code this} instance. */ public EqualsVerifier mustBeEqualsTo(final Object equalObject, final Object... equalObjects) { equalsObjects.add(equalObject); equalsObjects.addAll(asList(equalObjects)); for (Object eo : equalsObjects) { checkNotNull(eo, "equalObject"); } return this; } /** * Adds objects that are known to not be equals to {@code #object} to test against. * * @param unequalObject an object that is known to not be equals to {@code #object}. * @param unequalObjects objects that are known to not be equals to {@code #object}. * @return {@code this} instance. */ public EqualsVerifier mustNotBeEqualsTo(final Object unequalObject, final Object... unequalObjects) { unequalsObjects.add(unequalObject); unequalsObjects.addAll(asList(unequalObjects)); for (Object ue : unequalsObjects) { checkNotNull(ue, "unequalObject"); } return this; } /** * Asserts that {@code #object} correctly implements {@link Object#equals(Object)} and {@link Object#hashCode()} * using provided {@code #equalsObjects} and {@code #unequalsObjects}. */ public void verify() { verifyEquals(); verifyNotEquals(); } /** * Asserts that {@code #object} correctly implements {@link Object#equals(Object)} and {@link Object#hashCode()} * using provided {@code #equalsObjects}. */ private void verifyEquals() { // reflexive: for any non-null reference value {@code x}, {@code x.equals(x)} should return {@code true}. assertEquals(object, object); // symmetric: for any non-null reference values {@code x} and {@code y}, {@code x.equals(y)} should // return {@code true} if and only if {@code y.equals(x)} returns {@code true}. if (equalsObjects.isEmpty()) { logger.warn("There is no equals object to test against, use #mustBeEqualsTo()"); } else { // transitive: // - equals: for any non-null reference values {@code x}, {@code y}, and {@code z}, if {@code x.equals(y)} // returns {@code true} and {@code y.equals(z)} returns {@code true}, then {@code x.equals(z)} should return // {@code true}. // - hashCode: if two objects are equal according to the {@code equals(Object)} method, then calling the // {@code hashCode} method on each of the two objects must produce the same integer result. List objects = newArrayList(); objects.add(object); objects.addAll(equalsObjects); for (Object object1 : objects) { for (Object object2 : objects) { // consistent: for any non-null reference values {@code x} and {@code y}, multiple invocations of // {@code x.equals(y)} consistently return {@code true} or consistently return {@code false}, // provided no information used in {@code equals} comparisons on the objects is modified. assertEquals(object1, object2); assertEquals(object1, object2); assertEquals(object1, object2); } } } } /** * Asserts that {@code #object} correctly implements {@link Object#equals(Object)} and {@link Object#hashCode()} * using provided {@code #unequalsObjects}. */ private void verifyNotEquals() { // for any non-null reference value {@code x}, {@code x.equals(null)} should return {@code false}. assertNotEquals(object, null); if (unequalsObjects.isEmpty()) { logger.warn("There is no unequals object to test against, use #mustNotBeEqualsTo()"); } else { // transitive: // - hashCode: it is NOT required that if two objects are unequal according to the // {@link java.lang.Object#equals(java.lang.Object)} method, then calling the {@code hashCode} method on // each of the two objects must produce distinct integer results. However, the programmer should be aware // that producing distinct integer results for unequal objects may improve the performance of hash tables. for (Object unequalsObject : unequalsObjects) { // consistent: for any non-null reference values {@code x} and {@code y}, multiple invocations of // {@code x.equals(y)} consistently return {@code true} or consistently return {@code false}, // provided no information used in {@code equals} comparisons on the objects is modified. assertNotEquals(object, unequalsObject); assertNotEquals(object, unequalsObject); assertNotEquals(object, unequalsObject); } } } /** * Asserts that the actual object is equals to the expected one, and the opposite if expected is not {@code null}. * Asserts that the hashcode of the actual object is equals to the hashcode of the expected one. * * @param actual the actual object to test the expected object against. * @param expected the expected object to test the actual object against. */ private static void assertEquals(final Object actual, final Object expected) { // for code coverage, because AssertJ bypasses some things. assertThat(actual.equals(expected)).isTrue(); // symmetric if (expected != null) { assertThat(expected.equals(actual)).isTrue(); } if (expected != null) { assertThat(actual.hashCode()).isEqualTo(expected.hashCode()); // whenever it is invoked on the same object more than once during an execution of a Java application, the // {@code hashCode} method must consistently return the same integer, provided no information used in // {@code equals} comparisons on the object is modified. This integer need not remain consistent from one // execution of an application to another execution of the same application. int hashCode = actual.hashCode(); assertThat(actual.hashCode()).isEqualTo(hashCode); assertThat(actual.hashCode()).isEqualTo(hashCode); assertThat(actual.hashCode()).isEqualTo(hashCode); } } /** * Asserts that the actual object is not equals to the expected one, and the opposite if expected is not * {@code null}. * * @param actual the actual object to test the expected object against. * @param expected the expected object to test the actual object against. */ private static void assertNotEquals(final Object actual, final Object expected) { // for code coverage, because AssertJ bypasses some things. assertThat(actual.equals(expected)).isFalse(); // symmetric if (expected != null) { assertThat(expected.equals(actual)).isFalse(); } // optional but good practice if (expected != null) { assertThat(actual.hashCode()).isNotEqualTo(expected.hashCode()); } } }