JavaHamcrest icon indicating copy to clipboard operation
JavaHamcrest copied to clipboard

empty()-Matcher in contains()-Matcher works not with custom class

Open brainbytes42 opened this issue 6 years ago • 3 comments

I have a List of Lists and for that I wanted to check the contents. Therefore, I nested Matchers.empty() and Matchers.contains() inside Matchers.contains() - but the test failed, saying that the first element should have been empty, but was... empty. Well...

I've put together a short test-case, showing different related scenarios that are working (nested empty() with no contains() afterwards and vice versa) and the szenario not working. Interestingly, it does work with String, but not with a custom class.

@Test
public void emptyTest() throws Exception {
    List<List<Foo>> listOfLists = new ArrayList<>();
    Foo foo = new Foo();

    listOfLists.add(new ArrayList<>());
    // works as expected
    assertThat("Just empty()",listOfLists,contains(empty()));

    listOfLists.clear();
    listOfLists.add(Arrays.asList(foo));
    // works as expected
    assertThat("Just non-empty List",listOfLists,contains(contains(foo)));

    listOfLists.clear();
    listOfLists.add(new ArrayList<>());
    listOfLists.add(Arrays.asList(foo));
    // works as expected
    assertThat("first list is empty, check explicitly", listOfLists.get(0), empty());
    // works as expected - but why do I need the collection type?
    assertThat("empty and non-empty mixed, using emptyCollectionOf(Foo.class)",listOfLists,contains(emptyCollectionOf(Foo.class),contains(foo)));
    // fails, as empty() doesn't recognize the collection is empty - but only using a custom class... for List<List<String>> it works...
    assertThat("empty and non-empty mixed, using empty()",listOfLists,contains(empty(),contains(foo)));
}

class Foo{
    @Override
    public String toString() {
        return "Foo";
    }
}

Yielding following Message:

java.lang.AssertionError: empty and non-empty mixed, using empty()
Expected: iterable containing [<an empty collection>, <iterable containing [<Foo>]>]
     but: item 0: was <[]>

I'm using JUnit 4.12 Hamcrest 1.3 and Java 8.

brainbytes42 avatar Apr 25 '18 10:04 brainbytes42

It's hard for me to tell, but it looks like this is happening because java can't figure out the generic types. If I change the last line to:

assertThat("empty and non-empty mixed, using empty()",listOfLists,contains(Matchers.<Foo>empty(),Matchers.<Foo>contains(foo)));

Then the test passes. My IDE suggests that it may be the generic type on empty() that is the main culprit. When I remove the explicit generic type, its type hinting for the outer contains() goes from Iterable<? super List<Foo>> to Iterable<?>. I think, based on that, these matchers are getting passed to the contains(Object... elements) overload, which tries to see if the elements of the list are equal to the matchers, rather than checking using the matchers themselves.

I'm really not sure how to address the problem.

brownian-motion avatar Apr 20 '20 02:04 brownian-motion

I agree with @brownian-motion, the test works for me if I include the explicit types (which is ugly). I'm afraid I don't have any better advice than to swear at the Java generics implementation right now :-(

tumbarumba avatar May 01 '20 15:05 tumbarumba

Hi @tumbarumba, I would like to work on this issue and I have submitted a pull request.

HennyNile avatar Apr 25 '21 11:04 HennyNile