这几天我一直在使用 Junit 的 Matchers 功能。一切正常,但我正在寻找一个匹配器,它使用比较器进行比较,并且不依赖于对象等于方法。
我要更换
Assert.assertThat(one, CoreMatchers.equalTo(two)
类似(伪代码)的东西
Assert.assertThat(eins, CoreMatchers.equalTo(operand, new MyComparator())
您知道是否存在一种简单的开箱即用的解决方案?我在谷歌上没有找到,也不想写。
Hamcrest 2.0.0.0+ 现已支持此功能。
您可以使用 org.hamcrest.comparator.ComparatorMatcherBuilder 类来实现此目的,例如:
ComparatorMatcherBuilder builder = ComparatorMatcherBuilder.comparedBy(equivalenceComparator);
Assert.assertThat(eins, builder.comparesEqualTo(operand));
我在 hamcrest 1.3 中遇到了同样的问题,并通过编写一个匹配器解决了它,该匹配器遵循 IsEqual-Matcher 的代码,但使用给定的比较器而不是 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;
}
}));
}
}
另一种选择是对对象和可迭代对象使用AssertJ框架自定义比较策略。
assertThat(frodo).usingComparator(raceComparator).isEqualTo(sam);
assertThat(fellowshipOfTheRing).usingElementComparator(raceComparator).contains(sauron);
它还有逐个字段比较,例如isEqualToComparingOnlyGivenFields 和 isEqualToIgnoringGivenFields 比较器。
assertThat(frodo).isEqualToComparingOnlyGivenFields(sam, "race.name");
assertThat(frodo).isEqualToIgnoringGivenFields(sam, "name", "age");
因此,在大多数情况下,您无需自定义比较策略即可处理断言
我不知道汉克雷斯特有什么可以做到这一点。您可能需要编写一个自定义匹配器。需要考虑的一件事是:如果
equals
没有返回 true
,那么对象是否相等?如果您正在测试特定属性,您的自定义匹配器可能会更清晰,如 FeatureMatcher
(是否有一种简单的方法可以使用 Hamcrest 匹配字段?)。例如,如果测试是针对预期标签:
assertThat(eins, equalToUnderComparison("1", new LabelComparator());
可能更清楚:
assertThat(eins, hasLabel(eq("1")));
编写自定义匹配器不需要大量代码(编写自定义匹配器),如果它使代码更具可读性,则没有理由避免它。
我使用
verify()
方法偶然发现了相同的用例。决定实现一个自定义 ArgumentMatcher
,在比较之前简单地复制所有不感兴趣的类属性。
verify(testService).save(eqIgnoreA(saved));
public static Foo eqIgnoreA(Foo expected) {
return argThat(actual -> {
actual.setA(expected.getA());
return expected.equals(actual);
});
}