断言集合包含自定义对象 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 CreationsreflectEquals 匹配器:

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 方法。