Aaron
Aaron

Reputation: 462

Mockito.eq matcher seems to match against list reference, not value

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

Answers (2)

Jeff Bowman
Jeff Bowman

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

Andy Turner
Andy Turner

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

Related Questions