断言集合包含自定义对象 class,它不会覆盖 equals/hashcode
Assert collection contains object of custom class, which does not override equals/hashcode
我们有一个包含多个字段的自定义 class,出于业务领域的原因,我们无法覆盖 equals/hashcode 方法
然而,在单元测试期间,我们应该断言集合是否包含此 class
的项目
List<CustomClass> customObjectList = classUnderTest.methodUnderTest();
//create customObject with fields set to the very same values as one of the elements in customObjectList
//we should assert here that customObjectList contains customObject
但是,到目前为止,我们还没有找到任何可以在不覆盖 equals/hashcode 的情况下工作的解决方案,例如哈姆克雷斯特
assertThat(customObjectList, contains(customObject));
导致 AssertionError 引用
Expected: iterable containing [<CustomClass@578486a3>]
but: item 0: was <CustomClass@551aa95a>
有没有无需逐个字段比较的解决方案?
没有
Collection
接口的很多方法都是根据equals()
具体定义的。例如。 Collection#contains()
:
Returns true
if this collection contains the specified element. More
formally, returns true
if and only if this collection contains at
least one element e
such that (o==null ? e==null : o.equals(e))
.
只是for-each 逐个收集和比较。您可以将比较/相等逻辑存储到静态实用程序 class 中,例如 CustomClasses
或类似的,然后编写自定义 Hamcrest 匹配器。
或者,使用反射等号,例如来自 Apache Commons Lang, a Hamcrest extension, or (preferably) migrate to AssertJ, it has this functionality out of the box:
assertThat(ImmutableList.of(frodo))
.usingFieldByFieldElementComparator()
.contains(frodoClone);
它很笨拙,但对于测试来说已经足够好了。请不要在生产代码中使用。
如果您使用 Java8,您可以使用 Stream#anyMatch 和您自己的 customEquals 方法。像这样的东西会起作用-
assertTrue(customObjectList.stream()
.anyMatch(object -> customEquals(object,customObject)));
已更新 以反映 Holger 的评论
Fest Assertions 具有以下内容:
assertThat(expected).isEqualsToByComparingFields(actual);
我认为它在底层进行反射比较。我们遇到了类似的问题,这使我们免于编写自定义比较逻辑。
另一件事是使用适合您的 class 和案例的东西扩展您选择的断言框架。这种方法应该可以减少使用深度反射比较的一些性能开销。
我知道使用 Hamcrest 解决您的问题的两个解决方案。第一个测试项目的一些属性。
assertThat(customObjectList, contains(allOf(
hasProperty("propertyA", equalTo("someValue")),
hasProperty("propertyB", equalTo("anotherValue")))));
或者您可以使用来自 Nitor Creations 的 reflectEquals
匹配器:
assertThat(customObjectList, contains(reflectEquals(customObject)));
我想说谢谢大家的回复,有一些非常好的观点
但是,我在问题中忘记提到的是,我们的自定义 classes 是递归的,即包含其他自定义 class 类型的字段,同样的限制适用于等于和哈希码覆盖。不幸的是,上述两种开箱即用的解决方案(AssertJ、Nitor Creations)似乎都不支持深度比较
尽管如此,似乎仍然有解决方案,那就是 Unitils 的 ReflectionAssert class。以下似乎按我们预期的那样工作,甚至能够忽略集合中的元素顺序
assertReflectionEquals(Arrays.asList(customObject1, customObject3, customObject2), customObjectList, ReflectionComparatorMode.LENIENT_ORDER);
assertj is good at this. Especially its custom comparison strategy
private static class CustomClass {
private final String string;
CustomClass(String string) {
this.string = string;
}
// no equals, no hashCode!
}
@Test
public void assertjToTheRescue() {
List<CustomClass> list = Arrays.asList(new CustomClass("abc"));
assertThat(list).usingFieldByFieldElementComparator().contains(new CustomClass("abc"));
}
assertj 提供了许多其他 usingComparator
方法。
我们有一个包含多个字段的自定义 class,出于业务领域的原因,我们无法覆盖 equals/hashcode 方法
然而,在单元测试期间,我们应该断言集合是否包含此 class
的项目List<CustomClass> customObjectList = classUnderTest.methodUnderTest();
//create customObject with fields set to the very same values as one of the elements in customObjectList
//we should assert here that customObjectList contains customObject
但是,到目前为止,我们还没有找到任何可以在不覆盖 equals/hashcode 的情况下工作的解决方案,例如哈姆克雷斯特
assertThat(customObjectList, contains(customObject));
导致 AssertionError 引用
Expected: iterable containing [<CustomClass@578486a3>]
but: item 0: was <CustomClass@551aa95a>
有没有无需逐个字段比较的解决方案?
没有
Collection
接口的很多方法都是根据equals()
具体定义的。例如。 Collection#contains()
:
Returns
true
if this collection contains the specified element. More formally, returnstrue
if and only if this collection contains at least one elemente
such that(o==null ? e==null : o.equals(e))
.
只是for-each 逐个收集和比较。您可以将比较/相等逻辑存储到静态实用程序 class 中,例如 CustomClasses
或类似的,然后编写自定义 Hamcrest 匹配器。
或者,使用反射等号,例如来自 Apache Commons Lang, a Hamcrest extension, or (preferably) migrate to AssertJ, it has this functionality out of the box:
assertThat(ImmutableList.of(frodo))
.usingFieldByFieldElementComparator()
.contains(frodoClone);
它很笨拙,但对于测试来说已经足够好了。请不要在生产代码中使用。
如果您使用 Java8,您可以使用 Stream#anyMatch 和您自己的 customEquals 方法。像这样的东西会起作用-
assertTrue(customObjectList.stream()
.anyMatch(object -> customEquals(object,customObject)));
已更新 以反映 Holger 的评论
Fest Assertions 具有以下内容:
assertThat(expected).isEqualsToByComparingFields(actual);
我认为它在底层进行反射比较。我们遇到了类似的问题,这使我们免于编写自定义比较逻辑。
另一件事是使用适合您的 class 和案例的东西扩展您选择的断言框架。这种方法应该可以减少使用深度反射比较的一些性能开销。
我知道使用 Hamcrest 解决您的问题的两个解决方案。第一个测试项目的一些属性。
assertThat(customObjectList, contains(allOf(
hasProperty("propertyA", equalTo("someValue")),
hasProperty("propertyB", equalTo("anotherValue")))));
或者您可以使用来自 Nitor Creations 的 reflectEquals
匹配器:
assertThat(customObjectList, contains(reflectEquals(customObject)));
我想说谢谢大家的回复,有一些非常好的观点
但是,我在问题中忘记提到的是,我们的自定义 classes 是递归的,即包含其他自定义 class 类型的字段,同样的限制适用于等于和哈希码覆盖。不幸的是,上述两种开箱即用的解决方案(AssertJ、Nitor Creations)似乎都不支持深度比较
尽管如此,似乎仍然有解决方案,那就是 Unitils 的 ReflectionAssert class。以下似乎按我们预期的那样工作,甚至能够忽略集合中的元素顺序
assertReflectionEquals(Arrays.asList(customObject1, customObject3, customObject2), customObjectList, ReflectionComparatorMode.LENIENT_ORDER);
assertj is good at this. Especially its custom comparison strategy
private static class CustomClass {
private final String string;
CustomClass(String string) {
this.string = string;
}
// no equals, no hashCode!
}
@Test
public void assertjToTheRescue() {
List<CustomClass> list = Arrays.asList(new CustomClass("abc"));
assertThat(list).usingFieldByFieldElementComparator().contains(new CustomClass("abc"));
}
assertj 提供了许多其他 usingComparator
方法。