Sean Connolly
Sean Connolly

Reputation: 5801

Mockito verify with list that changes

I've got a simple Bar that does an action on some Strings:

public interface Bar {

  void action(List<String> strings);

}

And then my Foo which is under test. It builds up a list of Strings over time, and then eventually uses Bar to process them. When they've been processed, the queue is cleared.

public class Foo {

  private final Bar bar;
  private final List<String> strings;

  public Foo(Bar bar) {
    this.bar = bar;
    this.strings = new ArrayList<>();
  }

  public void addStrings(String... strings) {
      Collections.addAll(this.strings, strings);
  }

  public void processStrings() {
    bar.action(strings);
    strings.clear();
  }

}

I am trying to verify that the Bar.action gets called with the expected Strings. However, Mockito doesn't detect the true invocation with the actual parameters, because the list that was used when invoking Bar.action gets cleared afterwards.

I might be doing something wrong, but this smells like a bug to me. Here's the test:

@Test
public void shouldVerify() {
    // Given
    Bar bar = mock(Bar.class);
    Foo foo = new Foo(bar);
    foo.addStrings("Hello", "World");
    foo.addStrings("!!!");
    // When
    foo.processStrings();
    // Then
    verify(bar).action(Arrays.asList("Hello", "World", "!!!"));
}

Comparison Failure:

Expected :bar.action([Hello, World, !!!]);

Actual :bar.action([]);

To be sure, commenting out strings.clear() lets Mockito verify correctly.


I can get around this by making a new list to pass to Bar, e.g.:

public void processStrings() {
  bar.action(new ArrayList<>(strings));
  strings.clear();
}

Upvotes: 2

Views: 2127

Answers (1)

Indrek Ots
Indrek Ots

Reputation: 3911

Not totally sure here, but this seems to be a limitation of Mockito (but don't quote me on that). At first I thought this could be solved with an ArgumentCaptor but it captures the same instance and when you verify it, it has already been modified.

According to this answer you could use an Answer to verify the argument when the method is called. Since Bar::action has a return type of void, stubbing it requires a slightly different approach. Mockito docs suggest to use the doX() family of methods.

public void name() {
    Bar bar = mock(Bar.class);
    doAnswer(invocation -> {
        Object[] args = invocation.getArguments();
        List<String> list = (List<String>) args[0];
        assertThat(list.size(), is(3));
        return null;
    }).when(bar).action(anyList());

    Foo foo = new Foo(bar);
    foo.addStrings("Hello", "World");
    foo.addStrings("!!!");
    foo.processStrings();
}

In the doAnswer block you have access to the list when it was passed to the mock. Assertions could be done there or you could store the argument and do them later. I would say that the solution is not very elegant though.

Upvotes: 3

Related Questions