Testing in Hamcrest that exists only one item in a list with a specific property
Asked Answered
C

3

12

With Hamcrest we can easily test that exists at least one item in a list with a specific property, e.g.

List<Pojo> myList = ....

MatcherAssert.assertThat(myList, Matchers.hasItem(Matchers.<Pojo>hasProperty("fieldName", Matchers.equalTo("A funny string")))));

where the class Pojo is something like:

public class Pojo{
  private String fieldName;
}

That's nice, but how can I check that there is exactly one object in the list with the specificed properties?

Contour answered 13/4, 2015 at 15:40 Comment(0)
S
7

You might have to write your own matcher for this. (I prefer the fest assertions and Mockito, but used to use Hamcrest...)

For example...

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.core.IsCollectionContaining;

public final class CustomMatchers {

    public static <T> Matcher<Iterable<? super T>> exactlyNItems(final int n, Matcher<? super T> elementMatcher) {
        return new IsCollectionContaining<T>(elementMatcher) {
            @Override
            protected boolean matchesSafely(Iterable<? super T> collection, Description mismatchDescription) {
                int count = 0;
                boolean isPastFirst = false;

                for (Object item : collection) {

                    if (elementMatcher.matches(item)) {
                        count++;
                    }
                    if (isPastFirst) {
                        mismatchDescription.appendText(", ");
                    }
                    elementMatcher.describeMismatch(item, mismatchDescription);
                    isPastFirst = true;
                }

                if (count != n) {
                    mismatchDescription.appendText(". Expected exactly " + n + " but got " + count);
                }
                return count == n;
            }
        };
    }
}

You can now do...

    List<TestClass> list = Arrays.asList(new TestClass("Hello"), new TestClass("World"), new TestClass("Hello"));

    assertThat(list, CustomMatchers.exactlyNItems(2, hasProperty("s", equalTo("Hello"))));

Example fail output when the list is...

    List<TestClass> list = Arrays.asList(new TestClass("Hello"), new TestClass("World"));

...will be...

Exception in thread "main" java.lang.AssertionError: 
Expected: a collection containing hasProperty("s", "Hello")
     but: , property 's' was "World". Expected exactly 2 but got 1

(You might want to customise this a bit)

By the way, "TestClass" is...

public static class TestClass {
    String s;

    public TestClass(String s) {
        this.s = s;
    }

    public String getS() {
        return s;
    }
}
Sabrina answered 13/4, 2015 at 16:29 Comment(1)
It would be nice if you could do this out of the box, I can't see any built in matchers that will do this at the moment!Sabrina
L
6

Matchers.hasItems specifically checks to see if the items you provide exist in the collection, what you're looking for is Matchers.contains which ensures that the 2 collections are essentially the same - or in your case, equivalent according to the provided

Litt answered 13/4, 2015 at 16:51 Comment(3)
I don't understand why I should create a second collectionContour
I'm not following you, you don't have to create two collectionsLitt
You said: "which ensures that the 2 collections are essentially the same". Apart from that, contains is not the answer. It ensures that a list contains exactly the elements provided. From the doc, the assertion assertThat(Arrays.asList("foo", "bar"), contains("foo", "bar")) is successful, while assertThat(Arrays.asList("foo", "bar"), contains("foo")) fails, so how can I use it to be sure that contains exactly one instance of "foo"? I can't because I should also care of all the other elements in listContour
P
0

You can also use a Predicate<Pojo> and a filter:

Predicate<Pojo> predicate = pojo -> pojo.getField().equals("funny string");

long nrOfElementsSatisfyingPredicate = myList.stream()
                                      .filter(predicate::test)
                                      .count();

assertEquals(1, nrOfElementsSatisfyingPredicate);
Palaeo answered 15/8, 2016 at 13:9 Comment(3)
Hey, thanks for the solution, but that will not work with my question. You force the list to have size one, that is not what I was asking.Contour
Thanks for your comment, Roberto. I misunderstood the question. I hope this answer is more helpful.Palaeo
Hi Matthias, yes, that works. But the point was to use Hamcrest. If you introduce logic in the tests, then you must be sure sure that it is fine, otherwise you need testing also for it.Contour

© 2022 - 2024 — McMap. All rights reserved.