102

That should be really simple question I believe. But somehow I can't find answer in Google.

Assume that I have 2 Lists of Strings. First contains "String A" and "String B", second one contains "String B" and "String A" (notice difference in order). I want to test them with JUnit to check whether they contains exactly the same Strings.

Is there any assert that checks equality of Strings that ignore order? For given example org.junit.Assert.assertEquals throws AssertionError

java.lang.AssertionError: expected:<[String A, String B]> but was:<[String B, String A]>

Work around is to sort Lists firstly and then pass them to assertion. But I want my code to be as simple and clean as possible.

I use Hamcrest 1.3, JUnit 4.11, Mockito 1.9.5.

Naman
  • 27,789
  • 26
  • 218
  • 353
kukis
  • 4,489
  • 6
  • 27
  • 50
  • 3
    `list1.removeAll(list2)` should leave `list1` empty. I guess you can build on this to get what you want. – Rahul Apr 02 '14 at 09:45
  • 7
    `containsAll` and `removeAll` are `O(n²)` for lists while sorting them and test for equality is `O(nlogn)`. `Collections.sort(list1); Collections.sort(list2); assertTrue(list1.equals(list2));` is also clean. – Alexis C. Apr 02 '14 at 09:46
  • 1
    possible duplicate of [Hamcrest compare collections](http://stackoverflow.com/questions/21624592/hamcrest-compare-collections) – Joe Apr 05 '14 at 08:28
  • @SudoRahul - What if you do not want to modify a list by removing all ? – Erran Morad Dec 13 '17 at 07:23
  • @BoratSagdiyev - Since that was not a constraint from the OP, I suggested that. But if that is a constraint, then the accepted answer for this question solves the problem at hand. – Rahul Dec 14 '17 at 06:34

11 Answers11

108

As you mention that you use Hamcrest,
So I would pick one of the collection Matchers

import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
import static org.junit.Assert.assertThat;

public class CompareListTest {

    @Test
    public void compareList() {
        List<String> expected = Arrays.asList("String A", "String B");
        List<String> actual = Arrays.asList("String B", "String A");
        
        assertThat("List equality without order", 
            actual, containsInAnyOrder(expected.toArray()));
    }
    
}
Ahmed Nabil
  • 17,392
  • 11
  • 61
  • 88
cheffe
  • 9,345
  • 2
  • 46
  • 57
  • 5
    See also my answer http://stackoverflow.com/a/38262680/297710 which shows, how to improve Hamcrest matchers and avoid ".toArray()" in every assert with containsInAnyOrder – yvolk Jul 12 '16 at 19:27
71

You can use List.containsAll with JUnit's assertTrue to check that the first list contains every element from the second one, and vice versa.

assertEquals(expectedList.size(), actualList.size());
assertTrue(expectedList.containsAll(actualList));
assertTrue(actualList.containsAll(expectedList));

Hint:
This doesn't work with duplicates in the lists.

Ahmed Nabil
  • 17,392
  • 11
  • 61
  • 88
robertoia
  • 2,301
  • 23
  • 29
  • I accept this one because the code is the most clean I think. – kukis Apr 02 '14 at 09:54
  • @kukis See the comments in leventov's answer. – robertoia Apr 02 '14 at 10:16
  • True that. Don't really know which answer is correct. – kukis Apr 02 '14 at 10:23
  • 3
    @kukis It depends, do you want to check for duplicates? – robertoia Apr 02 '14 at 10:24
  • 4
    Yes, of course. 2 given Lists must be exactly the same just ignoring order. – kukis Apr 02 '14 at 10:26
  • 2
    @kukis Check ZouZou's comment on your question then. – robertoia Apr 02 '14 at 10:28
  • 1
    ..might include `assertEquals(first.size(), second.size())` ..then it should work as expected – definitely undefinable May 24 '16 at 15:16
  • 21
    This doesn't work with duplicates in the list. Here's an example to demonstrate: `List list1 = Arrays.asList("a", "a", "b");` `List list2 = Arrays.asList("a", "b", "b");` `assertEquals(list1.size(), list2.size());` `assertTrue(list1.containsAll(list2) && list2.containsAll(list1));` In this example, both assertions fail to detect that the lists are actually different. @AlexWorden mentions Apache Commons Collections' CollectionUtils.isEqualCollection() which, for this example, correctly detects that the collections are not equal. – desilvai Jun 30 '16 at 16:53
  • I hope the edit here would make more sense to use this method for comparison. – Naman Nov 02 '17 at 04:10
  • @nullpointer - Looks like you misunderstood desilvai's comment. Roberto's answer is still wrong even after your edits. Please revert the edits and downvote roberto's answer. – Erran Morad Dec 13 '17 at 07:16
  • @BoratSagdiyev Could you elaborate whats still wrong there? – Naman Dec 13 '17 at 08:00
  • @nullpointer - The assertions you added in your edit will fail to compare array lists correctly. To see it happening, run the code in selivai's comment. – Erran Morad Dec 13 '17 at 17:53
  • 1
    Another downside I experienced with this approach is that if the assertion fails, you do not get to see why (which objects were missing in the arrays). The `assertThat` with `containsInAnyOrder` does support this feature. – Jelle den Burger Mar 29 '22 at 08:47
13

Here's a solution that avoids quadratic complexity (iterating over the lists multiple times). This uses the Apache Commons CollectionUtils class to create a Map of each item to a frequency count itself in the list. It then simply compares the two Maps.

Assert.assertEquals("Verify same metrics series",
    CollectionUtils.getCardinalityMap(expectedSeriesList),
    CollectionUtils.getCardinalityMap(actualSeriesList));

I also just spotted CollectionUtils.isEqualCollection that claims to do exactly what is being requested here...

https://commons.apache.org/proper/commons-collections/apidocs/index.html?org/apache/commons/collections4/CollectionUtils.html

Tot Zam
  • 8,406
  • 10
  • 51
  • 76
Alex Worden
  • 3,374
  • 6
  • 34
  • 34
10

With AssertJ, containsExactlyInAnyOrder() or containsExactlyInAnyOrderElementsOf() is what you need :

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;

public class CompareListTest {

    @Test
    public void compareListWithTwoVariables() {
        List<String> expected = Arrays.asList("String A", "String B");
        List<String> actual = Arrays.asList("String B", "String A");
        Assertions.assertThat(actual)
                  .containsExactlyInAnyOrderElementsOf(expected);
    }

    @Test
    public void compareListWithInlineExpectedValues() {
        List<String> actual = Arrays.asList("String B", "String A");
        Assertions.assertThat(actual)
                  .containsExactlyInAnyOrder("String A", "String B");
    }    
}
davidxxx
  • 125,838
  • 23
  • 214
  • 215
6
    Collections.sort(excepted);
    Collections.sort(actual);
    assertEquals(excepted,actual);
Tinyfool
  • 1,460
  • 2
  • 18
  • 40
1

For a quick fix I would check both ways:

assertTrue(first.containsAll(second));
assertTrue(second.containsAll(first));

And trying with a situation where the number of the same elements is different (e.g. 1, 1, 2 and 1, 2, 2) I didn't get false positives.

Tot Zam
  • 8,406
  • 10
  • 51
  • 76
  • 4
    Your code still fails. See this example - @Test public void test1() { List list1 = Arrays.asList("a", "a", "b"); List list2 = Arrays.asList("a", "b", "b"); Assert.assertTrue(list1.containsAll(list2)); Assert.assertTrue(list2.containsAll(list1)); } – Erran Morad Dec 13 '17 at 07:20
1

You can use ListAssert that comes in junit-addons jar.

ListAssert.assertEquals(yourList, Arrays.asList(3, 4, 5));
runkar
  • 456
  • 1
  • 4
  • 15
1

Im late to the party but here's my solution using Junit only. Any thoughts are welcome.

List<String> actual = new ArrayList<>();
actual.add("A");
actual.add("A");
actual.add("B");

List<String> expected = new ArrayList<>();
actual.add("A");
actual.add("B");
actual.add("B");

//Step 1: assert for size
assertEquals(actual.size(), expected.size());

//Step 2: Iterate
for(String e: expected){
    assertTrue(actual.contains(e));
    actual.remove(e);
}
Boss Man
  • 587
  • 2
  • 12
0

Note that solution by Roberto Izquierdo has quadratic complexity in general. Solution on HashSets always has linear complexity:

assertTrue(first.size() == second.size() &&
        new HashSet(first).equals(new HashSet(second)));
leventov
  • 14,760
  • 11
  • 69
  • 98
  • 3
    That approach won't work. If first is ("String A") and second is ("String A", "String A") they are not the same lists. – Alexis C. Apr 02 '14 at 09:49
  • 4
    You can't check the size. If first is `("s1", "s2", "s3" ,"s1")` and second is `("s2", "s1", "s3" ,"s2");` they are not the same list. – Alexis C. Apr 02 '14 at 09:54
  • @ZouZou the accepted solution has the same problem. You suggested the only really correct solution. If you make an answer I will upvote it. – leventov Apr 02 '14 at 10:01
  • @ZouZou They are not the same list, but they contain exactly the same Strings. OP, clarify?. Also, make it an answer and I will upvote too :) didn't think of that. – robertoia Apr 02 '14 at 10:09
  • 3
    This is still not correct for all cases ("A", "A", "B") will compare as equal to ("A", "B", "B") – Tim B Feb 07 '17 at 16:58
0

Looks like the other answers either reference 3rd party utils, are incorrect, or are inefficient.

Here's a O(N) vanilla solution in Java 8.

public static void assertContainsSame(Collection<?> expected, Collection<?> actual)
{
    assert expected.size() == actual.size();

    Map<Object, Long> counts = expected.stream()
        .collect(Collectors.groupingBy(
                item -> item,
                Collectors.counting()));

    for (Object item : actual)
        assert counts.merge(item, -1L, Long::sum) != -1L;
}
Daniel Avery
  • 159
  • 1
  • 4
0

Using the updated hamcrest assertions

import org.junit.Test;

import java.util.Arrays;
import java.util.List;

import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;

import static org.hamcrest.MatcherAssert.assertThat;

public class TestCompareCollection {


    @Test
    public void compareList() {
        List<String> expected = Arrays.asList("String A", "String B");
        List<String> actual = Arrays.asList("String B", "String A");

        assertThat("List is not matching",
                actual, containsInAnyOrder(expected.toArray()));

    }

}