Reputation: 5801
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
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