确定集合包含未覆盖equals/hashcode方法的自定义类对象

6
我们有一个自定义类,其中包含几个字段。由于业务领域的原因,我们无法覆盖equals/hashcode方法。然而,在单元测试期间,我们应该断言集合中是否包含此类的项。
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的解决方案,例如Hamcrest。

assertThat(customObjectList, contains(customObject));

导致断言错误的结果引用

Expected: iterable containing [<CustomClass@578486a3>]
but: item 0: was <CustomClass@551aa95a>

有没有不需要逐个字段比较的解决方案?
6个回答

4

如果您正在使用Java8,您可以使用Stream#anyMatch和您自己的customEquals方法。类似以下内容将起作用 -

   assertTrue(customObjectList.stream()
                 .anyMatch(object -> customEquals(object,customObject)));

更新:根据Holger的评论进行了修改


1
anyMatch 需要一个谓词。修复后,filter 就变得过时了:customObjectList.stream().anyMatch(object -> customEquals(object,customObject)) - Holger
你是对的(谢谢!),我在脑海中混合匹配了anyMatch和findFirst。已相应更新。下面是findFirst,但我认为anyMatch更好。 assertTrue(customObjectList.stream() .filter(object -> customEquals(object,customObject)) .findFirst().isPresent()); - John McClean

4
我要感谢所有回复,有一些非常好的观点。
然而,在我的问题中我忘了提到我们的自定义类是递归的,即包含其他自定义类类型的字段,对于这些字段同样需要重写equals和hashcode。不幸的是,提到的两个开箱即用的解决方案(AssertJ、Nitor Creations)似乎都不支持深度比较。
尽管如此,仍然存在一个解决方案,那就是Unitils中的ReflectionAssert类。以下代码似乎按照我们的期望工作,甚至能够忽略集合中元素的顺序。
assertReflectionEquals(Arrays.asList(customObject1, customObject3, customObject2), customObjectList, ReflectionComparatorMode.LENIENT_ORDER);

3

AssertJ在这方面很擅长。特别是它的自定义比较策略

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


1
Fest Assertions具有以下特点:
assertThat(expected).isEqualsToByComparingFields(actual);

我认为它在底层进行反射比较。我们遇到了类似的问题,这使我们免于编写自定义比较逻辑。

另一种方法是使用您选择的断言框架扩展与您的确切类和情况相关的内容。这种方法应该减少使用深度反射比较的性能开销。


我在我使用的fest版本中找不到这个方法。你用的是哪个版本,Danail?我有 festAssert: 'org.easytesting:fest-assert-core:2.0M10' - fIwJlxSzApHEZIl
@anon58192932 我认为应该在那里。至少我可以在文档中找到:http://javadox.com/org.easytesting/fest-assert-core/2.0M10/org/fest/assertions/api/ObjectAssert.html#isEqualsToByComparingFields(T) - Danail Alexiev
也许我的导入有问题,IntelliJ的自动完成也没有帮助我找到它们,就像我想象的那样。我很快会再试一次。但是我们可以假设assertThat(x).isEqualTo(y)默认情况下不使用深度比较吗? - fIwJlxSzApHEZIl

1

我知道两种使用Hamcrest解决您问题的方法。第一种测试该项的一些属性。

assertThat(customObjectList, contains(allOf(
    hasProperty("propertyA", equalTo("someValue")),
    hasProperty("propertyB", equalTo("anotherValue")))));

或者您可以使用Nitor CreationsreflectEquals匹配器:

assertThat(customObjectList, contains(reflectEquals(customObject)));

0

不。

Collection 接口的许多方法都是根据 equals() 定义的。例如 Collection#contains()

如果此集合包含指定的元素,则返回 true。更正式地说,仅当此集合包含至少一个元素 e 使得 (o==null ? e==null : o.equals(e)) 时,才返回 true

只需使用 for-each 循环遍历集合并逐个比较字段即可。您可以将比较/相等逻辑存储到静态实用程序类(如 CustomClasses 或类似类)中,然后编写自定义 Hamcrest 匹配器。

或者,使用一个反射相等方法,例如来自 Apache Commons Lang 的工具类,一个 Hamcrest 扩展,或者(最好的选择)迁移到 AssertJ,它已经默认提供了此功能:has this functionality out of the box
assertThat(ImmutableList.of(frodo))
        .usingFieldByFieldElementComparator()
        .contains(frodoClone);

这是一种不太正规和靠谱的方法,但对于测试来说已经足够了。请不要在生产代码中使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接