How to check if collection contains items in given order using Hamcrest
Asked Answered
W

6

54

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")); 
Writing answered 25/3, 2013 at 6:44 Comment(3)
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.Equiangular
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.Broadbrim
In this question other matchers are explained https://mcmap.net/q/157199/-hamcrest-compare-collectionsSelfexpression
R
65

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);
  }
Romantic answered 25/3, 2013 at 6:58 Comment(3)
+1 Yes, update to the latest JUnit and Hamcrest, though contains has been around for a while.Radioactivate
What version of Hamcrest? For me, 1.3 is causing: java.lang.AssertionError: Expected: iterable containing ["foo", "boo"] but: Not matched: "bar"Better
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.Septa
B
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.

Blunder answered 18/6, 2015 at 12:14 Comment(0)
L
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"));
}
Loretaloretta answered 25/3, 2013 at 7:0 Comment(1)
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 ...Appendix
B
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.

Better answered 23/4, 2014 at 19:16 Comment(5)
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?Comprador
@Comprador 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.Dump
@Dump 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.Comprador
@Comprador the presence of "bar" could be an implementation detail that is irrelevant for the test at hand. Making the test depend on this would make it more brittle than it has to be.Rabelais
@StefanReisner If that were the case, the code being tested would be doing too many things. If you feel your unit test should only be testing part of your SUT's behaviour, that means your SUT is too big and you need to fix that, not write partial tests.Comprador
P
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)

Pratincole answered 12/1, 2015 at 17:56 Comment(1)
Welcome to Stack Overflow. Here, it is better to include important parts of code in your answer.Spearman
S
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.

Stagy answered 30/10, 2019 at 19:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.