Mockito not returning correct result from mocked method with multiple "when" conditions

Viewed 31

I have a simple DTO class:

@Data // lombok
public class MyObj {
    private int id;
    private String someProperty;
}

and a class with some computation logic for the DTO object:

public class MyClass {
    public String doSomething(MyObj obj) {
        // some calculations
    }
}

I tried to mock MyClass to use it in some unit tests, but encountered a weird problem. Therefore, I created this minimal test code to illustrate the issue:

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {
    private final MyObj myObjFirst = new MyObj();
    private final MyObj myObjSecond = new MyObj();

    @Mock
    private MyClass myClass;

    @Before
    public void setUp() {
        doReturn("OTHER")
                .when(myClass)
                .doSomething(any(MyObj.class));
        doReturn("FIRST")
                .when(myClass)
                .doSomething(myObjFirst);
        doReturn("SECOND")
                .when(myClass)
                .doSomething(myObjSecond);
    }

    @Test
    public void doSomething() {
        assertEquals("FIRST", myClass.doSomething(myObjFirst)); // fail, actual value "SECOND"
        assertEquals("SECOND", myClass.doSomething(myObjSecond));
        assertEquals("OTHER", myClass.doSomething(new MyObj()));
        assertEquals("OTHER", myClass.doSomething(new MyObj()));
    }
}

For some reason, myClass.doSomething(myObjFirst) produces the string "SECOND" instead of the expected "FIRST", therefore the test fails at the first assert.

What am I doing wrong?

1 Answers

Solution

When stubbing, wrap the expected objects into Mockito.same to match them by reference (see this answer for different matchers):

@Before
public void setUp() {
    doReturn("OTHER")
            .when(myClass)
            .doSomething(any(MyObj.class));
    doReturn("FIRST")
            .when(myClass)
            .doSomething(same(myObjFirst)); // <-- HERE
    doReturn("SECOND")
            .when(myClass)
            .doSomething(same(myObjSecond)); // <-- AND HERE
}

Explanation

Mockito seems to use the equals method to detect the given argument to the mocked method (reference to docs needed).

Due to Lombok's @Data annotation, a MyObj.equals implementation is generated, which compares the objects by their fields id and someProperty. Because both fields are null on myObjFirst and myObjSecond, and because last stubbing has the highest importance, Lombok first compares the argument given to myClass.doSomething against myObjSecond, immediately finding a match and returning "SECOND".

If Lombok's @Data annotation was omitted from MyObj, the above code would work as-is, because then MyObj.equals method would default to object reference comparison, making myObjFirst not equal to myObjSecond.

In this case, however, myObjFirst equals myObjSecond equals new MyObj(), unless their properties are set to different values.

Related