How to JUnit test that two List<E> contain the same elements in the same order?
Context
I am writing a simple JUnit test for the MyObject
class.
A MyObject
can be created from a static factory method that takes a varargs of String.
MyObject.ofComponents("Uno", "Dos", "Tres");
At any time during the existence of MyObject
, clients can inspect the parameters it was created by in the form of a List<E>, through the .getComponents()
method.
myObject.ofComponents(); // -> List<String>: { "Uno", "Dos", "Tres" }
In other words, a MyObject
both remembers and exposes the list of parameters that brought it into existence. More details about this contract:
- The order of
getComponents
will be the same as the one chosen for object creation - Duplicate subsequent String components are allowed and retained in order
- Behaviour on
null
is undefined (other code guarantees nonull
gets to the factory) - There are no ways to alter the list of components after object instantiation
I am writing a simple test that creates a MyObject
from a list of String and checks that it can return the same list via .getComponents()
. I do this immediately but this is supposed to happen at a distance in a realistic code path.
Code
Here my attempt:
List<String> argumentComponents = Lists.newArrayList("One", "Two", "Three");
List<String> returnedComponents =
MyObject.ofComponents(
argumentComponents.toArray(new String[argumentComponents.size()]))
.getComponents();
assertTrue(Iterables.elementsEqual(argumentComponents, returnedComponents));
Question
- Is Google Guava
Iterables.elementsEqual()
the best way, provided I have the library in my build path, to compare those two lists? this is something I have been agonizing about; should I use this helper method which goes over an Iterable<E>.. check size and then iterate running.equals()
.. or any other of the methods that an Internet search suggests? what's the canonical way to compare lists for unit tests?
Optional insights I would love to get
- Is the method test designed reasonably? I am not an expert in JUnit!
- Is
.toArray()
the best way to convert a List<E> to a varargs of E?
Solution 1:
Why not simply use List#equals
?
assertEquals(argumentComponents, imapPathComponents);
Contract of List#equals
:
two lists are defined to be equal if they contain the same elements in the same order.
Solution 2:
I prefer using Hamcrest because it gives much better output in case of a failure
Assert.assertThat(listUnderTest,
IsIterableContainingInOrder.contains(expectedList.toArray()));
Instead of reporting
expected true, got false
it will report
expected List containing "1, 2, 3, ..." got list containing "4, 6, 2, ..."
IsIterableContainingInOrder.contain
Hamcrest
According to the Javadoc:
Creates a matcher for Iterables that matches when a single pass over the examined Iterable yields a series of items, each logically equal to the corresponding item in the specified items. For a positive match, the examined iterable must be of the same length as the number of specified items
So the listUnderTest
must have the same number of elements and each element must match the expected values in order.
Solution 3:
The equals() method on your List implementation should do elementwise comparison, so
assertEquals(argumentComponents, returnedComponents);
is a lot easier.
Solution 4:
org.junit.Assert.assertEquals()
and org.junit.Assert.assertArrayEquals()
do the job.
To avoid next questions: If you want to ignore the order put all elements to set and then compare: Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))
If however you just want to ignore duplicates but preserve the order wrap you list with LinkedHashSet
.
Yet another tip. The trick Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))
works fine until the comparison fails. In this case it shows you error message with to string representations of your sets that can be confusing because the order in set is almost not predictable (at least for complex objects). So, the trick I found is to wrap the collection with sorted set instead of HashSet
. You can use TreeSet
with custom comparator.