Assert collection contains object of custom class, which does not override equals/hashcode
Asked Answered
E

6

6

We have a custom class with several fields, for which we cannot override equals/hashcode methods for business domain reasons

Nevertheless, during unit testing we should assert on whether a collection contains an item of this 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

However, so far we did not find any solution that would work without overriding equals/hashcode, e.g. Hamcrest

assertThat(customObjectList, contains(customObject));

results in AssertionError citing

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

Is there a solution to this without having to compare field-by-field?

Eneidaenema answered 21/9, 2015 at 15:0 Comment(0)
E
4

I would like to say thank you for all the responses, there have been some really good points made

However, what I forgot to mention in my question, is that our custom classes are recursive, that is containing fields of other custom class types, for which the same restriction applies regarding equals and hashcode overriding. Unfortunately neither of the mentioned out-of-the box solutions (AssertJ, Nitor Creations) seem to support deep comparison

Nevertheless, there still seems to be a solution, and that is ReflectionAssert class from Unitils. The following seems to work as we expected, even able to ignore element order in the collection

assertReflectionEquals(Arrays.asList(customObject1, customObject3, customObject2), customObjectList, ReflectionComparatorMode.LENIENT_ORDER);
Eneidaenema answered 22/9, 2015 at 8:21 Comment(0)
L
4

If you are using Java8 you could use Stream#anyMatch and your own customEquals method. Something like this would work -

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

UPDATED to reflect Holger's comment

Leyva answered 21/9, 2015 at 15:23 Comment(2)
anyMatch requires a predicate. Fixing that makes the filter obsolete: customObjectList.stream().anyMatch(object -> customEquals(object,customObject))Fornix
You're right (thanks!), mentally mixed and matched anyMatch and findFirst. Updated accordingly. FindFirst below, but I think anyMatch is better. assertTrue(customObjectList.stream() .filter(object -> customEquals(object,customObject)) .findFirst().isPresent());Leyva
E
4

I would like to say thank you for all the responses, there have been some really good points made

However, what I forgot to mention in my question, is that our custom classes are recursive, that is containing fields of other custom class types, for which the same restriction applies regarding equals and hashcode overriding. Unfortunately neither of the mentioned out-of-the box solutions (AssertJ, Nitor Creations) seem to support deep comparison

Nevertheless, there still seems to be a solution, and that is ReflectionAssert class from Unitils. The following seems to work as we expected, even able to ignore element order in the collection

assertReflectionEquals(Arrays.asList(customObject1, customObject3, customObject2), customObjectList, ReflectionComparatorMode.LENIENT_ORDER);
Eneidaenema answered 22/9, 2015 at 8:21 Comment(0)
L
3

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 offers many other usingComparator methods.

Luker answered 16/3, 2018 at 10:22 Comment(0)
R
1

Fest Assertions has the following:

assertThat(expected).isEqualsToByComparingFields(actual);

I think it does reflection comparison under the hood. We had a similar issue and this saved us from writing custom comparison logic.

The other thing is to extend an assertion framework of your choice with something for your exact class and case. This approach should shave off some of the performance overhead of using deep reflection comparison.

Ricker answered 21/9, 2015 at 15:31 Comment(3)
I can't find this method in the version of fest that I'm using. What version are you using Danail? I have festAssert: 'org.easytesting:fest-assert-core:2.0M10'.Orvil
@anon58192932 I think it should be there. At least I can find in the docs: javadox.com/org.easytesting/fest-assert-core/2.0M10/org/fest/…Ricker
Maybe my imports are off and IntelliJ's autocomplete isn't helping me find them as I'd assume it would. I'll give it another whirl here soon. But can we assume that assertThat(x).isEqualTo(y) does NOT use a deep comparison by default?Orvil
B
1

I know two solutions for your problems that are using Hamcrest. The first one tests some properties of the item.

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

Or you can use the reflectEquals matcher from Nitor Creations:

assertThat(customObjectList, contains(reflectEquals(customObject)));
Babism answered 21/9, 2015 at 19:55 Comment(0)
S
0

No.

Many methods of the Collection interface are specifically defined in terms of equals(). E.g. 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)).

Just for-each the collection and compare field by field. You can stash the comparing / equaling logic into a static utility class like CustomClasses or similar, and then write a custom Hamcrest matcher.

Alternatively, use a reflective equals, e.g. from 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);

It's hacky and dodgy, but good enough for tests. Please, don't use in production code.

Servility answered 21/9, 2015 at 15:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.