Consider this method, I want to write a unit test for it.
public void update(List toUpdate) {
//...
Collections.sort(toUpdate, new SpecificComparator());
//...
}
I would like to ensure we are ordering the given list using the SpecificComparator implementation. Anyway to do this?
I was thinking of using a factory to retrieve the SpecificComparator implementation, so that I could verify with Mockito that we are calling the factory, but that would not ensure me that the list is ordered given this comparator, we could imagine that someone is calling the factory in the method but not ordering the list with it...
Or, I could also verify the ordering of the toUpdate
list object, order another list with the same comparator in my unit test and check they are ordered in the same way?
So many answer, all containing one piece of "truth" but missing others. Sad.
Thus: you should absolutely avoid using a mock to test your comparator on sorting calls. Instead you do test two aspects:
assertThat(actualList, is(expectedList));
Long story short: the Comparator itself is a unit. You start testing that thing; with all corner cases and what not. Then you make sure that the methods that are supposed to sort lists do come back with a sorted list.
And if your code is such that you still need to think about mocking then chances are that your design could be improved to be easy-to-test (and not hard-to-test).
The idea is to have lists sorted already as IF the particular comparator was used. This is important because the behavior of the comparator should not change and if it does then new requirements should be written which means your lists used for testing must change. However for the most part you shouldn't be changing the behavior of a single comparator too often. Therefore you should create lists that are sorted already as if the comparator was used, then you can call the following method
public boolean unitTestComparator(List sorted, Comparator comp)
{
List shuffled = new List(sorted);
Collections.shuffle(shuffled);
return sorted.equals(Collections.sort(shuffled, comp));
}
Now you can use multiple tests for various lists to exercise all edge cases for your different comparators. The key to all this is that you know what the lists should look like after the comparator is used, the only tedious part is finding all the edge cases per comparator. You could also run a for loop and on this method to test it as many times as you'd like because you provide the correct format of the list to the method. All it does is randomize the shuffle.
Note this is random testing, you could also add another parameter to the method which could be the list shuffled in the way you want, to find particular edge cases.
I would say that this isn't a reasonable thing to want to do.
A unit test should only be testing a class at its public interfaces, and since the comparator is a hidden implementation detail, it's none of the unit test's business how the sort order is achieved.
This means that if the implementation changes some day, to achieve the same sort order by some other means, the test will continue to pass -- and that's how things should be.
So, you should test that the output is sorted in the order you expect, but you should not assert anything about an object that's private to the method.
Of course, if you made the Comparator
part of the class's API, it would be a different matter:
public class Updater {
private final Comparator sortComparator;
public Updater(Comparator sortComparator) {
this.sortComparator = sortComparator;
}
public void update(List toUpdate) {
//...
Collections.sort(toUpdate, sortComparator);
//...
}
}
... then it would be appropriate to pass a mock Comparator
to the constructor, and assert that it has been used -- because now it's of interest to the caller, that the Comparator
it's passed in is the one that's being used, and therefore something that should be tested.
which you mentioned above I called it as a "cross test", but I think it is unnecessary, if you really want to achieve it, I give it to you, and let you to think it more. test update a list twice with different comparators and assert the results is you expected.
public class CrossingTest {
@Test
public void sortWithSpecificComparator() throws Throwable {
List<Integer> list = asList(0, 1, 2);
List<Integer> reversedList = asList(2, 1, 0);
Comparator<Integer> originalOrder = (a, b) -> a < b ? -1 : 1;
assertThat(update(list, with(originalOrder)), equalTo(list));
assertThat(update(list, with(originalOrder.reversed()))
, equalTo(reversedList));
}
private List<Integer> update(List<Integer> input, Comparator comparator) {
List<Integer> list = new ArrayList<>(input);
SUT it = new SUT(comparator);
it.update(list);
return list;
}
private Comparator with(Comparator comparator) { return comparator; }
}
class SUT {
private Comparator comparator;
public SUT(Comparator comparator) {
this.comparator = comparator;
}
public void update(List toUpdate) {
Collections.sort(toUpdate, comparator);
}
}
Although you can mock your new object with the following:
SpecificComparator comparator = Mockito.mock(SpecificComparator.class);
Mockito.whenNew(SpecificComparator.class).thenReturn(comparator);
Best approach is always to check the ordering of elements after the method is called rather than checking when/how comparator is used. You can do that by using equals
method of List , this is what it says:
Compares the specified object with this list for equality. Returns true if and only if the specified object is also a list, both lists have the same size, and all corresponding pairs of elements in the two lists are equal.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.