Mockito 没有从具有多个 "when" 条件的模拟方法返回正确的结果

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

我有一个简单的 DTO class:

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

和一个 class 以及 DTO 对象的一些计算逻辑:

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

我试图模拟MyClass在一些单元测试中使用它,但遇到了一个奇怪的问题。因此,我创建了这个最小的测试代码来说明这个问题:

@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()));
    }
}

出于某种原因,myClass.doSomething(myObjFirst) 生成字符串 "SECOND" 而不是预期的 "FIRST",因此测试在第一个断言处失败。

我做错了什么?

解决方案

存根时,将预期的对象包装到 Mockito.same 中以通过引用匹配它们( 用于不同的匹配器):

@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
}

说明

Mockito 似乎使用 equals 方法来检测模拟方法的给定参数(需要参考文档)。

由于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 首先将给定myClass.doSomething 的参数与myObjSecond 进行比较,立即找到匹配并返回"SECOND"

如果从 MyObj 中省略 Lombok 的 @Data 注释,上面的代码将按原样运行,因为 MyObj.equals 方法将默认为对象引用比较,使得 myObjFirst 不等于 myObjSecond.

然而,在这种情况下,myObjFirst 等于 myObjSecond 等于 new MyObj(),除非它们的属性设置为不同的值。