1

I have a callback interface that has a method which takes a List object. I want to use InOrder to verify that the callback method is called the appropriate number of times, in the correct order, with the correct parameters.

The problem is that Mockito seems to be getting confused because I am passing the same List object into the method and modifying it between invocations. When I call InOrder.verify(), I want to verify the value of the List object at the time in which that method invocation was executed.

Code:

public class Test {
    @Test
    public void test() {
        Callback callback = Mockito.mock(Callback.class);
        InOrder inOrder = Mockito.inOrder(callback);

        {
            List<String> list = new ArrayList<String>(); //note: this List object is inaccessible from the unit test in my real use-case
            callback.foo("name1", list);
            list.add("value");
            callback.foo("name2", list);
        }

        inOrder.verify(callback).foo("name1", Arrays.<String> asList()); //fails here
        inOrder.verify(callback).foo("name2", Arrays.asList("value"));
    }

    interface Callback {
        void foo(String name, List<String> list);
    }
}

Error message:

Argument(s) are different!  Wanted:
callback.onFoo([]);
Actual invocation has different arguments:
callback.onFoo([value]);

Passing a copy of the List object into each callback method invocation makes the test pass. But I don't want to create a new List every time the method is called.

I looked at the MockSettings object you can pass into Mockito.mock(), but didn't see anything that might help.

This question is similar to mine. But the solution does not verify the content of the collection that was passed into each method call--it only verifies the fact that some collection was passed into it (anyCollectionOf()). It verifies the end result, but not the individual invocations.

This answer seemed to provide a potential solution. It uses ArgumentCaptor to capture the object that is passed into the method. But when I use it to verify the first method call, it returns the List object after all the modifications were made to it, thus failing the test. I need it to return a List object whose state matches the List object's state at that precise invocation.

ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);

inOrder.verify(callback).foo(Mockito.eq("name1"), argument.capture());
assertEquals(Arrays.asList(), argument.getValue()); //fails: "expected: <[]> but was: <[value]>

inOrder.verify(callback).foo(Mockito.eq("name2"), argument.capture());
assertEquals(Arrays.asList("value"), argument.getValue());

How can I get this test to pass without sacrificing any granularity?

Community
  • 1
  • 1
Michael
  • 34,873
  • 17
  • 75
  • 109
  • If you are going to use my anser in the duplicate suggested by me, you would create a copy of the list in the capturing function. And afterwards during the verification phase you could inspect those copies for them to be correct. – SpaceTrucker Aug 05 '16 at 11:19
  • Thank you @SpaceTrucker. Not ideal, but it works. – Michael Aug 06 '16 at 14:13

1 Answers1

0

The question that @SpaceTrucker linked to provided a solution.

Basically, create a custom Answer for the method and store a copy of the List argument every time the method is called. Then, verify that all of the copies are what you expect them to be. It's not ideal, but it works.

@Test
public void test() {
    Callback callback = Mockito.mock(Callback.class);

    final List<List<String>> listParameters = new ArrayList<List<String>>();
    Mockito.doAnswer(new Answer<Void>() {
        public Void answer(InvocationOnMock invocation) throws Throwable {
            List<String> list = (List<String>) invocation.getArguments()[1];
            listParameters.add(new ArrayList<String>(list));
            return null;
        }
    }).when(callback).foo(Mockito.anyString(), Mockito.anyList());

    {
        List<String> list = new ArrayList<String>(); //note: this List object is inaccessible from the unit test in my real use-case
        callback.foo("name1", list);
        list.add("value");
        callback.foo("name2", list);
    }

    InOrder inOrder = Mockito.inOrder(callback);
    inOrder.verify(callback).foo(Mockito.eq("name1"), Mockito.anyList());
    inOrder.verify(callback).foo(Mockito.eq("name2"), Mockito.anyList());

    Assert.assertEquals(Arrays.asList(
        Arrays.asList(),
        Arrays.asList("value")
    ), listParameters);
}
Community
  • 1
  • 1
Michael
  • 34,873
  • 17
  • 75
  • 109