Junit Matcher for comparators?
Asked Answered
A

5

16

For several days I am using now Junit's Matchers feature. Everything is working OK but I am looking for a matcher which uses a comparator for comparing and which does not rely on the objects equals methodes.

I want to replace

Assert.assertThat(one, CoreMatchers.equalTo(two)

with something like (pseudocode)

Assert.assertThat(eins, CoreMatchers.equalTo(operand, new MyComparator())

Do you know whether there exists an easy out of the box solution? I did not find one in google and do not want to write one.

Asteria answered 30/7, 2013 at 14:24 Comment(2)
You can try to write your own matcher, extending BaseMatcher.Emigration
Surprised it doesn't exist. Please post your solution so next person could use it.Adhesion
T
15

This is now supported in Hamcrest 2.0.0.0+.

You can use the org.hamcrest.comparator.ComparatorMatcherBuilder class to achieve this, for instance:

ComparatorMatcherBuilder builder = ComparatorMatcherBuilder.comparedBy(equivalenceComparator);
Assert.assertThat(eins, builder.comparesEqualTo(operand));
Tolu answered 25/3, 2015 at 12:47 Comment(2)
This link is dead.Garlandgarlanda
Corrected dead linkTolu
C
1

I had the same problem in hamcrest 1.3 and solved it by writing a matcher, that follows the code of the IsEqual-Matcher but uses a given Comparator instead of Object#equals().

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.junit.Assert;

import java.lang.reflect.Array;
import java.util.Comparator;

/**
 * Is the value equal to another value, as tested by the
 * given Comparator?<br/>
 * Based on the example of {@link org.hamcrest.core.IsEqual}.
 *
 * @author Serhat Cinar
 */
public class IsEqualWithComparator<T> extends BaseMatcher<T> {
    private final Object expectedValue;
    private final Comparator<T> comparator;

    public IsEqualWithComparator(T equalArg, Comparator<T> comparator) {
        expectedValue = equalArg;
        this.comparator = comparator;
    }

    @Override
    public boolean matches(Object actualValue) {
        return areEqual(actualValue, expectedValue, comparator);
    }

    @Override
    public void describeTo(Description description) {
        description.appendValue(expectedValue);
    }

    private static boolean areEqual(Object actual, Object expected, Comparator comparator) {
        if (actual == null) {
            return expected == null;
        }

        if (expected != null && isArray(actual)) {
            return isArray(expected) && areArraysEqual(actual, expected, comparator);
        }

        return comparator.compare(actual, expected) == 0;
    }

    private static boolean areArraysEqual(Object actualArray, Object expectedArray, Comparator comparator) {
        return areArrayLengthsEqual(actualArray, expectedArray) && areArrayElementsEqual(actualArray, expectedArray, comparator);
    }

    private static boolean areArrayLengthsEqual(Object actualArray, Object expectedArray) {
        return Array.getLength(actualArray) == Array.getLength(expectedArray);
    }

    private static boolean areArrayElementsEqual(Object actualArray, Object expectedArray, Comparator comparator) {
        for (int i = 0; i < Array.getLength(actualArray); i++) {
            if (!areEqual(Array.get(actualArray, i), Array.get(expectedArray, i), comparator)) {
                return false;
            }
        }
        return true;
    }

    private static boolean isArray(Object o) {
        return o.getClass().isArray();
    }

    @Factory
    public static <T> Matcher<T> equalTo(T operand, Comparator<T> comparator) {
        return new IsEqualWithComparator<>(operand, comparator);
    }

    public static void main(String argv[]) {
        Assert.assertThat("abc", IsEqualWithComparator.equalTo("ABC", new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.equalsIgnoreCase(o2) ? 0 : -1;
            }
        }));
    }
}
Choice answered 9/12, 2016 at 13:33 Comment(0)
A
1

Another one option is using AssertJ framework custom comparison strategy for object and iterables.

assertThat(frodo).usingComparator(raceComparator).isEqualTo(sam);
assertThat(fellowshipOfTheRing).usingElementComparator(raceComparator).contains(sauron);

It has also Field by field comparisons, e.g. isEqualToComparingOnlyGivenFields and isEqualToIgnoringGivenFields comparators.

assertThat(frodo).isEqualToComparingOnlyGivenFields(sam, "race.name");
assertThat(frodo).isEqualToIgnoringGivenFields(sam, "name", "age");

So, in most case, you can handle assertion without custom comparison strategy

Autotransformer answered 15/11, 2017 at 15:21 Comment(1)
Assertj not AspectJBrunk
G
0

I'm not aware of anything in Hamcrest to do that. You may need to write a custom matcher. One thing to consider: if equals is not returning true, are the objects equal? If you're testing for specific properties, your custom matcher may be clearer as a FeatureMatcher (Is there a simple way to match a field using Hamcrest?). For example, if the test is for an expected label:

assertThat(eins, equalToUnderComparison("1", new LabelComparator());

may be clearer as:

assertThat(eins, hasLabel(eq("1")));

Writing custom matchers doesn't take a lot of code (Writing custom matchers) and there's no reason to avoid it if it makes code more readable.

Godart answered 3/8, 2013 at 12:26 Comment(1)
It's very actual for JPA model instances where you cannot implement equals/hashCode, based on business logic. See #5032114Autotransformer
D
0

I have stumbled upon the same use case with verify() method. Decided to implement a custom ArgumentMatcher that simply copies all uninterested class properties before comparing them.

verify(testService).save(eqIgnoreA(saved));

public static Foo eqIgnoreA(Foo expected) {
  return argThat(actual -> {
    actual.setA(expected.getA());
    return expected.equals(actual);
  });
}
Darbies answered 8/8, 2022 at 9:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.