assertThat - hamcrest - check if list is sorted
Asked Answered
H

6

21

Ok I think its going to be a short question. I have an ArrayList that I sorted by date, of course I see it works but I would also like to write a test for it.

I want to check if next value (date) in my list is lower then previous one. I am able to do that with using some for s and adding temp list, but I'm wondering if there's a easier solution. I read in hamrest documentation that there's somethink like contains(hamrest contains) that iterate through an object (list,map etc) but still I have no idea what to do next.

Hua answered 6/11, 2013 at 14:43 Comment(2)
Provide some code bro :) you can get help quicklySashasashay
I didnt provide any code because I thought i dont have to since my question is about any list (collection) that contains element and I want to compare element from list with next one in the same listHua
T
20

[First Option]: you can write your own Matcher. Something like (disclaimer: this is just a sample code, it is not tested and may be not perfect):

@Test
  public void theArrayIsInDescendingOrder() throws Exception
  {
    List<Integer> orderedList = new ArrayList<Integer>();
    orderedList.add(10);
    orderedList.add(5);
    orderedList.add(1);
    assertThat(orderedList, isInDescendingOrdering());
  }

  private Matcher<? super List<Integer>> isInDescendingOrdering()
  {
    return new TypeSafeMatcher<List<Integer>>()
    {
      @Override
      public void describeTo (Description description)
      {
        description.appendText("describe the error has you like more");
      }

      @Override
      protected boolean matchesSafely (List<Integer> item)
      {
        for(int i = 0 ; i < item.size() -1; i++) {
          if(item.get(i) <= item.get(i+1)) return false;
        }
        return true;
      }
    };
  }

This example is with Integers but you can do it with Dates easily.

[Second option], based on the reference to contains in the OP's question: you can create a second list, ordering the original one, than using assertThat(origin, contains(ordered)). This way the eventual error is more precisely described since, if an element is not in the expected order, it will be pointed out. For example, this code

@Test
  public void testName() throws Exception
  {
    List<Integer> actual = new ArrayList<Integer>();
    actual.add(1);
    actual.add(5);
    actual.add(3);
    List<Integer> expected = new ArrayList<Integer>(actual);
    Collections.sort(expected);
    assertThat(actual, contains(expected.toArray()));
  }

will generate the description

java.lang.AssertionError: 
Expected: iterable containing [<1>, <3>, <5>]
     but: item 1: was <5>
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
    at org.junit.Assert.assertThat(Assert.java:865)
    at org.junit.Assert.assertThat(Assert.java:832)
    ...
Talesman answered 6/11, 2013 at 15:11 Comment(3)
second option works, therefore first is overkill / too specializedPhallic
How would you apply the second solution to test Comparable objects?Dysgenics
In the mean time I am now using AssertJ more and more, they provide the ability to specify the comparator to use while making assertions. I would have to do some research to provide an Hamcrest solution. SorryTalesman
L
16

There's an open request for such a matcher, but unfortunately it isn't implemented yet.

I'd go for something much simpler - copy the list, sort it, and compare to the original:

@Test
public void testListOrder() {
    ArrayList<SomeObject> original = ...;
    ArrayList<SomeObject> sorted = new ArrayList<SomeObject>(original);
    Collections.sort(sorted);
    Assert.assertEquals ("List is not sorted", sorted, original);
}

EDIT:
@dsncode has a good point in the comments - while (relatively) elegant, this solution is not designed with any performance consideration. If the list isn't too large it should be OK, but if the list is large, sorting it may be costly. If the list is large, it may be a good idea to iterate over it directly, and fail the test if you encounter an element that is smaller than the previous one. E.g.:

assertTrue(() -> {
    Iterator<SomeClass> iter = list.iterator();

    SomeClass prev = null;
    if (iter.hasNext()) {
        prev = iter.next();
    }

    SomeClass curr = null;
    while (iter.hasNext()) {
        curr = iter.next();
        if (curr.compareTo(prev) < 0) {
            return false;
        }
        prev = curr;
    }

    return true;
});
Linell answered 6/11, 2013 at 14:50 Comment(3)
Yes you are right its pretty simeple solution, I was only wondering if theres a way to compare only elements from one list (for e.g i have 20.01.1991 and 20.02.1991 in my list and compare both of them)Hua
unfortunately, by sorting, this solution adds time complexity from linear time check to a possible n^2. So, if tested element list are guaranteed to by small. then it should be fine. Otherwise, I wouldn't advice to do this approach.Tarkington
@Tarkington that's a good point - I was going for code brevity, under the assumption that you won't be testing with huge lists. I've edited my answer with an example of an O(n) assertion.Linell
O
12

Also it's possible to check it using GUAVA:

import com.google.common.collect.Ordering;

...

assertTrue(Ordering.natural().isOrdered(list));

More information here: How to determine if a List is sorted in Java?

Oceania answered 13/1, 2017 at 10:13 Comment(0)
B
7

You can use hamcrest-more-matchers (available on Maven Central). It has two utility methods to validate sorted collections: softOrdered (allow equal sequential items) and strictOrdered (does not allow equal items). Usage example:

import com.github.seregamorph.hamcrest.OrderMatchers.*;

@Test
public void softOrderedEqualShouldSuccess() {
    // success
    assertThat(Arrays.asList(1, 1, 2), softOrdered());
    // fails with diagnostics
    // java.lang.AssertionError: 
    // Expected: Strictly ordered by natural comparator
    //     but: Found equal elements 1 and 1
    assertThat(Arrays.asList(1, 1, 2), strictOrdered());
}

Or it can be nested:

@Test
public void nestedCollectionShouldMatchOrderedItem() {
    List<Iterable<Integer>> nested = Arrays.asList(
            Arrays.asList(3, 2, 1),
            Arrays.asList(1, 2, 3)
    );

    // success
    assertThat(nested, hasItem(strictOrdered()));
    // fails with diagnostics
    // java.lang.AssertionError:
    // Expected: every item is Softly ordered by natural comparator
    //     but: an item Found unordered elements 3 and 2
    assertThat(nested, everyItem(softOrdered()));
}

By default natural comparator is used, but also it can be custom:

import static java.util.Comparator.comparing;

@Test
public void stringsShouldMatchOrderByLength() {
    List<String> list = Arrays.asList("abc", "ab", "a");

    assertThat(list, strictOrdered(comparing(String::length).reversed()));
}

Badenpowell answered 9/10, 2020 at 8:44 Comment(0)
S
3

For small collections I suggest to provide expected collection hardcoded in code. This is unit test and should not contains any logic. After that you can compare two collections. ( use hamcrest to check equivalent)

Sundew answered 6/11, 2013 at 14:50 Comment(0)
F
3

I had a similar problem, I just check if the next date value in milliseconds is bigger/smaller then the previous one in that list.

/**
 * Test sort by date
 */
@Test
public void findAllMessagesSortByDate() {
    Collection<Message> messages = messageService.getAllMessagesSortedByDate();
    long previousTime = messages.iterator().next().getDate().getTimeInMillis();
    for (Message message : messages) {
        assertTrue(message.getDate.getTimeInMillis() <= previousTime);
        previousTime = message.getMessageFolderTs().getTimeInMillis();
    }
}
Farcy answered 30/10, 2014 at 10:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.