How to verify method calls with collections where content changes?

978 views
Skip to first unread message

Jumwah

unread,
Jun 6, 2014, 12:54:43 AM6/6/14
to moc...@googlegroups.com

Using Mockito I'm trying to verify that certain objects are added and removed from another object. But the function that does this work stores a instance of a collection, updates the contents of this collection, and passes it to my object.

When I then try to verify that addAll and removeAll has been called with the correct objects, it seems Mockito is actually holding onto a reference to the collection, therefore has no idea what the collection contained when the methods were called. A simplified example :

class MySUT
{
    private final Collection<Object> objects = new ArrayList<>();
    public MySUT(Object firstObject)
    {
        objects.add(firstObject);
    }

    public void addNewObject(Collection<Object> other, Object newObject)
    {
        other.removeAll(objects);
        objects.clear();

        objects.add(newObject);
        other.addAll(objects);
    }
}

@Test
public void test()
{
    Object firstObject = mock(Object.class);
    Object newObject = mock(Object.class);
    Collection<Object> myObject = mock(Collection.class);

    MySUT sut = new MySUT(firstObject);

    sut.addNewObject(myObject, newObject);

    verify(myObject).removeAll(eq(Collections.singletonList(firstObject)));
    verify(myObject).addAll(eq(Collections.singletonList(newObject)));
}

This test fails claiming that the removeAll method was called with a list containing newObject which clearly it wasn't.

I can't use ArgumentCaptor either as it works the same way - so how can I verify the correct things have been passed to myObject (obviously in the real code myObject is not a simple collection)?

Marcin Grzejszczak

unread,
Jun 6, 2014, 1:07:05 AM6/6/14
to moc...@googlegroups.com
Hi!

Why not just pass a normal, instantiated collection and assert its content at the end of the test?


--
You received this message because you are subscribed to the Google Groups "mockito" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mockito+u...@googlegroups.com.
To post to this group, send email to moc...@googlegroups.com.
Visit this group at http://groups.google.com/group/mockito.
For more options, visit https://groups.google.com/d/optout.

Jumwah

unread,
Jun 6, 2014, 1:12:30 AM6/6/14
to moc...@googlegroups.com
Hi, thanks for the reply.

As I said, the collection 'myObject' is just a simplified example, in my actual code this is not a collection and thus needs to be a mock.

Thanks,
James.

Jumwah

unread,
Jun 6, 2014, 3:28:27 AM6/6/14
to moc...@googlegroups.com
OK, I've worked it out, and thought I'd share in case anyone else has the same problem. I've made the stub return an answer, and in the answer implementation added the arguments into a new list. Then I assert against that. It's a bit nasty, but it works.

class MySUT
{
    private final Collection<Object> objects = new ArrayList<>();
    public MySUT(Object firstObject)
    {
        objects.add(firstObject);
    }

    public void addNewObject(Collection<Object> other, Object newObject)
    {
        other.removeAll(objects);
        objects.clear();

        objects.add(newObject);
        other.addAll(objects);
    }
}

@Test
public void test()
{
    Object firstObject = mock(Object.class);
    Object newObject = mock(Object.class);
    Collection<Object> myObject = mock(Collection.class);

    MySUT sut = new MySUT(firstObject);

    final List<Object> removeAllResult = new ArrayList<>();
    when(myObject.removeAll(anyCollectionOf(Object.class))).thenAnswer(new Answer<Object>()
    {
        @Override
        public Object answer(InvocationOnMock invocation) throws Throwable
        {
            removeAllResult.addAll((Collection<Object>) invocation.getArguments()[0]);
            return null;
        }
    });

    sut.addNewObject(myObject, newObject);

    verify(myObject).removeAll(anyCollectionOf(Object.class));
    verify(myObject).addAll(anyCollectionOf(Object.class));

    assertThat(removeAllResult.get(0), equalTo(firstObject));
}

If anyone else has a better/alternative solution, I'd still like to know :)

Brice Dutheil

unread,
Jun 27, 2014, 11:53:58 AM6/27/14
to moc...@googlegroups.com

That’s one of the sacrifices Mockito has to make in order to have this API (stub first, act, then verify). It has the benefit to favor immutable design. However when the code has mutable object graph then indeed an custom answer to verify at interaction times. However this kind of coding is not elegant flow wise, I would strongly advise to refactor the code.

Also note that having a anonymous class in the test is not really readable and may confuse on the intent of this answer. I suggest to have a method that return an instance of this answer (you can also use the then alias of thenAnswer):

@Test public void test() {
    // ...

    when(myObject.removeAll(anyCollectionOf(Object.class))).then(recordFirstArgIn(removeAllResult));

    sut.addNewObject(myObject, newObject);

    // ...
}

// ...
Answer<Object> recordFirstArgIn(Collection<Object>) {

    return new Answer<Object>() {
        @Override public Object answer(InvocationOnMock invocation) throws Throwable { ... }
    }

Cheers,
Brice


-- Brice


--
Reply all
Reply to author
Forward
0 new messages