简体   繁体   中英

Unit Test : check if a particular Comparable has been called

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:

  1. You write unit tests for your comparator. Heck, the comparator has one method; with very clear defined semantics. You focus on writing unit tests to test that thing in isolation. Because that is A) easy to do and B) the whole idea of unit tests: you do as much testing on the smallest unit as possible.
  2. You write a few unit tests to ensure your "plumbing" is correct. And you don't mock anything in there. You make sure that a list with known content gets sorted using your comparator; and then you 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM