We came across really nasty problem with Mockito.
Code:
public class Baz{
private Foo foo;
private List list;
public Baz(Foo foo){
this.foo = foo;
}
public void invokeBar(){
list = Arrays.asList(1,2,3);
foo.bar(list);
list.clear();
}
}
public class BazTest{
@Test
void testBarIsInvoked(){
Foo mockFoo = mock(Foo.class);
Baz baz = new Baz(mockFoo);
baz.invokeBar();
verify(mockFoo).bar(Arrays.asList(1,2,3));
}
}
This causes error message like:
Arguments are different! Wanted:
foo.bar([1,2,3]);
Actual invocation has different arguments:
foo.bar([]);
What just happened:
Mockito records reference to list
rather than copy of list
, so in the code above Mockito verifies against modified version (empty list, []
) instead to the one actually passed during invocation ([1,2,3]
)!
Question:
Is there any elegant and clean solution to this problem other than doing a defensive copy like below (which actually helps but we don't like this solution)?
public void fun(){
list = Arrays.asList(1,2,3);
foo.bar(new ArrayList(list));
list.clear();
}
We don't want to modify correct production code and reduce its performance only to fix technical problem with test.
I'm asking this question here because it seems to be possibly common problem with Mockito. Or we just do something wrong?
PS. This is not a real code so please don't ask why we create a list and then clear it etc. In real code we have a real need to do something similar :-).
Mockito verifies argument values in natural java style: by using an equals() method.
A stub is a fake class that comes with preprogrammed return values. It's injected into the class under test to give you absolute control over what's being tested as input. A typical stub is a database connection that allows you to mimic any scenario without having a real database.
Yes, they are.
Mockito allows us to partially mock an object. This means that we can create a mock object and still be able to call a real method on it. To call a real method on a mocked object we use Mockito's thenCallRealMethod().
The solution here is to use a customized answer. Two code samples: the first is the test classes used, the second is the test.
First, the test classes:
private interface Foo
{
void bar(final List<String> list);
}
private static final class X
{
private final Foo foo;
X(final Foo foo)
{
this.foo = foo;
}
void invokeBar()
{
// Note: using Guava's Lists here
final List<String> list = Lists.newArrayList("a", "b", "c");
foo.bar(list);
list.clear();
}
}
On to the test:
@Test
@SuppressWarnings("unchecked")
public void fooBarIsInvoked()
{
final Foo foo = mock(Foo.class);
final X x = new X(foo);
// This is to capture the arguments with which foo is invoked
// FINAL IS NECESSARY: non final method variables cannot serve
// in inner anonymous classes
final List<String> captured = new ArrayList<String>();
// Tell that when foo.bar() is invoked with any list, we want to swallow its
// list elements into the "captured" list
doAnswer(new Answer()
{
@Override
public Object answer(final InvocationOnMock invocation)
throws Throwable
{
final List<String> list
= (List<String>) invocation.getArguments()[0];
captured.addAll(list);
return null;
}
}).when(foo).bar(anyList());
// Invoke...
x.invokeBar();
// Test invocation...
verify(foo).bar(anyList());
// Test arguments: works!
assertEquals(captured, Arrays.asList("a", "b", "c"));
}
Of course, being able to write such a test requires that you are able to inject into your "outer object" sufficient state so that the test is meaningful... Here it is relatively easy.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With