EqualsTestUtil.java

package net.trajano.commons.testing;

import java.util.concurrent.Callable;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * This provides utility methods that ensure the {@link #equals(Object)} and
 * {@link #hashCode()} are implemented correctly.
 *
 * @author Archimedes Trajano
 */
public final class EqualsTestUtil {

    /**
     * Builds two objects the same way and checks if they are equal.
     *
     * @param <T>
     *            type
     * @param objectBuilder
     *            object builder
     */
    public static <T> void assertEqualsImplementedCorrectly(final Callable<T> objectBuilder) {

        assertEqualsImplementedCorrectly(objectBuilder, objectBuilder);
    }

    /**
     * Builds two objects and ensures that they are equal.
     *
     * @param <T>
     *            type
     * @param objectBuilder1
     *            first object builder
     * @param objectBuilder2
     *            second object builder
     */
    public static <T> void assertEqualsImplementedCorrectly(final Callable<T> objectBuilder1,
            final Callable<T> objectBuilder2) {

        try {
            final T o1 = objectBuilder1.call();
            final T o2 = objectBuilder2.call();
            assertEqualsImplementedCorrectly(o1, o2);
        } catch (final Exception e) {
            throw new AssertionError(e);
        }
    }

    /**
     * Take two objects and ensure their are implemented correctly. Warnings are
     * suppressed as this block of code will do things that normal developers
     * are not supposed to do, but are needed to ensure that
     * {@link #equals(Object)} is implemented correctly.
     * <p>
     * The generic check is not put in to allow testing with different classes
     * </p>
     *
     * @param o1
     *            first object
     * @param o2
     *            second object
     */
    @SuppressWarnings("all")
    @SuppressFBWarnings()
    public static void assertEqualsImplementedCorrectly(final Object o1,
            final Object o2) {

        // symmetric
        if (!o1.equals(o2)) {
            throw new AssertionError("Expected " + o1 + " ==  " + o2);
        }
        if (!o2.equals(o1)) {
            throw new AssertionError("Expected " + o2 + " ==  " + o1);
        }

        // this == object tests
        if (!o1.equals(o1)) {
            throw new AssertionError("Expected " + o1 + " ==  " + o1);
        }
        if (!o2.equals(o2)) {
            throw new AssertionError("Expected " + o2 + " ==  " + o2);
        }

        // Different class checks
        if (o1.equals(new EqualsTestUtil())) {
            throw new AssertionError("Type of " + o1 + " does not match expected class");
        }
        if (o2.equals(new EqualsTestUtil())) {
            throw new AssertionError("Type of " + o2 + " does not match expected class");
        }

        // Null tests done poorly but will at least trigger the right paths.

        // CHECKSTYLE:OFF
        if (o1.equals(null)) {
            throw new AssertionError("Did not expect " + o1 + " == null");
        }
        if (o2.equals(null)) {
            throw new AssertionError("Did not expect " + o2 + " == null");
        }
        // CHECKSTYLE:ON

        // hash code validity
        if (o1.hashCode() != o2.hashCode()) {
            throw new AssertionError(String.format("Expected hash code of %s (%d) == %s (%d)", o1, o1.hashCode(), o2, o2.hashCode()));
        }
    }

    /**
     * Take a single object and ensure its equality is implemented correctly.
     *
     * @param <T>
     *            type
     * @param o
     *            object
     */
    public static <T> void assertEqualsImplementedCorrectly(final T o) {

        assertEqualsImplementedCorrectly(o, o);
    }

    /**
     * Prevent instantiation of utility class.
     */
    private EqualsTestUtil() {

    }
}