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?

JeanValjean
  • 17,172
  • 23
  • 113
  • 157

3 Answers3

6

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;
    }
}
BretC
  • 4,141
  • 13
  • 22
  • 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! – BretC Apr 13 '15 at 16:34
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

tddmonkey
  • 20,798
  • 10
  • 58
  • 67
  • I don't understand why I should create a second collection – JeanValjean Apr 13 '15 at 16:54
  • 1
    I'm not following you, you don't have to create two collections – tddmonkey Apr 13 '15 at 17:28
  • You said: "_which ensures that the 2 collections are essentially the same_". Apart from that, [contains](http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matchers.html#contains(E...)) 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 list – JeanValjean Apr 14 '15 at 06:14
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);
Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
  • 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. – JeanValjean Aug 15 '16 at 17:35
  • Thanks for your comment, Roberto. I misunderstood the question. I hope this answer is more helpful. – Matthias Braun Aug 16 '16 at 07:56
  • 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. – JeanValjean Aug 17 '16 at 08:34