53

How to check using Hamcrest if given collection is containing given items in given order? I tried hasItems but it simply ignores the order.

List<String> list = Arrays.asList("foo", "bar", "boo");

assertThat(list, hasItems("foo", "boo"));

//I want this to fail, because the order is different than in "list"
assertThat(list, hasItems("boo", "foo")); 
Mariusz Jamro
  • 30,615
  • 24
  • 120
  • 162
  • 1
    Consider changing the accepted answer. Usually, when we search for a matcher, we favor those already in the API, leaving custom matchers to the unavoidable cases. – acdcjunior Oct 30 '13 at 13:36
  • 1
    The accepted answer does not answer the question. The question asks for a matcher that merely checks that the expected items are contained in the actual list in the given order, but not that these are ALL the actual items. The `Matchers.contains` method checks that the list contains exactly the expected items. – Christian Semrau Apr 18 '16 at 12:53
  • In this question other matchers are explained https://stackoverflow.com/a/44030511/1195507 – rvazquezglez Oct 21 '17 at 00:49

6 Answers6

64

You can use contains matcher instead, but you probably need to use latest version of Hamcrest. That method checks the order.

assertThat(list, contains("foo", "boo"));

You can also try using containsInAnyOrder if order does not matter to you.

That's the code for contains matcher:

  public static <E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers)
  {
    return IsIterableContainingInOrder.contains(itemMatchers);
  }
Andrey Adamovich
  • 20,285
  • 14
  • 94
  • 132
  • 1
    +1 Yes, update to the latest JUnit and Hamcrest, though `contains` has been around for a while. – David Harkness Mar 26 '13 at 11:09
  • 2
    What version of Hamcrest? For me, 1.3 is causing: java.lang.AssertionError: Expected: iterable containing ["foo", "boo"] but: Not matched: "bar" – Kevin Pauli Apr 22 '14 at 22:22
  • 7
    the solution won't work for a given subset of the result list, because contains-Matcher fails with any extra item not given in the expected item array. – vvursT Apr 07 '15 at 06:49
10

To check tha collection contains items in expected (given) order you can use Hamcrest's containsInRelativeOrder method.

From javadoc:

Creates a matcher for Iterable's that matches when a single pass over the examined Iterable yields a series of items, that contains items logically equal to the corresponding item in the specified items, in the same relative order For example: assertThat(Arrays.asList("a", "b", "c", "d", "e"), containsInRelativeOrder("b", "d")).

Actual for Java Hamcrest 2.0.0.0.

Hope this helps.

nndru
  • 2,057
  • 21
  • 16
2

You need to implement a custom Matcher, something like this

class ListMatcher extends BaseMatcher {
    String[] items;

    ListMatcher(String... items) {
        this.items = items;
    }

    @Override
    public boolean matches(Object item) {
        List list = (List) (item);
        int l = -1;
        for (String s : items) {
            int i = list.indexOf(s);
            if (i == -1 || i < l) {
                return false;
            }
            l = i;
        }
        return true;
    }

    @Override
    public void describeTo(Description description) {
        // not implemented
    }
}

@Test
public void test1() {
    List<String> list = Arrays.asList("foo", "bar", "boo");
    Assert.assertThat(list, new ListMatcher("foo", "boo"));
    Assert.assertThat(list, new ListMatcher("boo", "foo"));
}
Mariusz Jamro
  • 30,615
  • 24
  • 120
  • 162
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • 6
    OMG. This matcher is ugly and buggy. It should extend TypeSafeMatcher. Or least check instance of item in the matches() methods - currently you can get ClassCastExecption. And it should be a generic type, because currently it works for Strings only. And it should handle null. And the matches() has O(n^2) complexity. And ... – Marek Nov 06 '14 at 10:42
1

The accepted answer is not working for me. It still fails, saying

Expected: iterable containing ["foo", "boo"] but: Not matched: "bar"

So I wrote my own IsIterableContainingInRelativeOrder, and submitted it as a patch.

Kevin Pauli
  • 8,577
  • 15
  • 49
  • 70
  • Actually, you should change your assertion to also look for "bar". It's your unit test, surely you should know that the outcome of your SUT includes the element "bar" being in the list? – Frans Apr 11 '18 at 14:52
  • @Frans no he shouldn't. True tests are meant to check only one thing and fail only if that thing doesn't work. If this thing is whether or not "boo" goes after "foo", then nothing unrelated should present in assertions. – RomanMitasov Jan 05 '23 at 09:49
  • @RomanMitasov That's not what the single assertion principle means. The OP hasn't actually told us what he is testing, but it is reasonable to assume that given a single input, the output of his subject under test is 100% deterministic. Therefore there is no reason not to assert that that output is what you expect it to be (all of it, not just a subset). And that is exactly what `contains` does. Violation of single assertion principle would be running the subject under test with two inputs in the same tests, but that's not what I'm proposing. – Frans Jan 05 '23 at 16:06
1

I found a solution at http://www.baeldung.com/hamcrest-collections-arrays

Look for the section that has an example with strict order.

List<String> collection = Lists.newArrayList("ab", "cd", "ef");
assertThat(collection, contains("ab", "cd", "ef"));

Basically you need to use the contains Matcher (org.hamcrest.Matchers.contains)

kommradHomer
  • 4,127
  • 5
  • 51
  • 68
Andreas Guther
  • 422
  • 4
  • 7
0

You can combine is and equalTo of matchers library. The assert statement looks longer but the error message is better. Since contains is fail first it breaks when it finds the first mismatch and wouldn't find failures further down the list. Where as is and equalTo will print the entire list from which you see all the mismatches.

Example using contains

List<String> list = Arrays.asList("foo", "bar", "boo");
assertThat(list, contains("foo", "boo", "bar"));

Gives the following error message:

Expected: iterable containing ["foo", "boo", "bar"]
 but: item 1: was "bar"

Example using is and equalTo

List<String> list = Arrays.asList("foo", "bar", "boo");
assertThat(list, is(equalTo(Lists.newArrayList("foo", "boo", "bar"))));

Gives the following error message:

Expected: is <[foo, boo, bar]>
     but: was <[foo, bar, boo]>

The second method doesn't tell you the index where the mismatch is but to me this is still better as you can fix the test by looking at the failure message just once. Whereas in method one, you'll first fix index 1, run the test, detect mismatch at index 2 and do the final fix.

Selvaram G
  • 727
  • 5
  • 18