Reputation: 462
I have a method addToClonedList(List)
in class App
that calls method add(List)
in class Dependency
. I want to unit test addToClonedList(List)
, and I want to create a mock Dependency
object (called mockDependency
) and verify that the method mockDependency.add(List)
is called with the correct argument. However, since the List
instance referenced by clonedList
gets a new element (100
) added to if after the call in question, Mockito thinks that the 2-element List
was passed to the mockDependency
, and the test fails. In reality, the List
passed to mockDependency.add(List)
only contained one element when it was called. Is this expected Mockito behavior? Is there a more standard way to test that I've passed the correct List
to mockDependency.add(List)
?
See my code structure and test output below:
main/java/App.java:
import java.util.*;
public class App {
Dependency dataLayer = new Dependency();
void addToClonedList(List<Integer> integerList) {
List<Integer> clonedList = new ArrayList<Integer>(integerList);
dataLayer.add(clonedList);
clonedList.add(100);
}
}
main/java/Dependency.java:
import java.util.List;
public class Dependency {
void add(List<Integer> integerList) {
//
}
}
test/java/AppTest.java:
import java.util.*;
import org.junit.*;
import org.mockito.*;
public class AppTest {
@InjectMocks
App app;
@Mock
Dependency mockDependency;
@Before
public void beforeEachTest() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testWithVerify() {
List<Integer> originalList = new ArrayList<>();
originalList.add(1);
app.addToClonedList(originalList);
Mockito.verify(mockDependency).add(Mockito.eq(originalList));
}
}
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.athenahealth</groupId>
<artifactId>mockitotest</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.testifyproject.mock</groupId>
<artifactId>mockito</artifactId>
<version>0.9.8</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
Test output:
Argument(s) are different! Wanted:
mockDependency.add([1]);
-> at AppTest.testWithVerify(AppTest.java:25)
Actual invocation has different arguments:
mockDependency.add([1, 100]);
-> at App.addToClonedList(App.java:8)
Comparison Failure:
Expected :mockDependency.add([1]);
Actual :mockDependency.add([1, 100]);
Upvotes: 8
Views: 12883
Reputation: 95704
As Andy Turner notes, the problem is that Mockito only keeps a reference to the value passed, without cloning it. This makes sense, because it would be wasteful for immutable value objects like String, and would be practically impossible for certain references like Thread.
However, you can write your own Answer to make that copy yourself:
List<Integer> listSnapshot;
@Test
public void testWithVerify() {
List<Integer> originalList = new ArrayList<>();
originalList.add(1);
Mockito.when(mockDependency.add(any())).thenAnswer(invocation => {
listSnapshot = new ArrayList<>((List<Integer>) (invocation.getArguments()[0]));
});
app.addToClonedList(originalList);
assertEquals(originalList, listSnapshot);
}
Upvotes: 4
Reputation: 140494
Mockito isn't magical.
When the add
method is called on the mock, the mock simply holds onto a reference to the value of the parameter. The eq
matcher then compares to see whether the parameter you pass into verify
equals that value, using equals
. It can't do any better than this because, in general, it doesn't know how to take a copy of the parameter.
If you modify the value passed into the mock afterwards, Mockito doesn't know that you've done this, and still just compares the two references using equals
.
You call the method on the mock with clonedList
, and then add a value into clonedList
, but not integerList
. As such, when you compare clonedList
to originalList
using eq
, they are different, and hence the check doesn't pass.
As suggested, if you don't add to clonedList
, the test passes; and you can then see that it's not comparing using ==
by replacing eq
with same
(the test will then fail).
Upvotes: 2