Mockito未从模拟方法中返回正确的结果,其中有多个“when”条件。

huangapple go评论52阅读模式
英文:

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

问题

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()));
    }
}
英文:

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

得分: 3

Solution

当进行存根化时,将预期的对象包装在 Mockito.same 中,以通过引用进行匹配(请参阅此答案以获取不同的匹配器示例):

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

Explanation

Mockito似乎使用equals方法来检测传递给模拟方法的参数(需要查看文档以确认)。

由于Lombok的@Data注解,生成了一个MyObj.equals实现,它通过它们的字段idsomeProperty来比较对象。因为myObjFirstmyObjSecond上的两个字段都是null,并且因为最后的存根具有最高的优先级,所以Lombok首先将传递给myClass.doSomething的参数与myObjSecond进行比较,立即找到匹配并返回"SECOND"

如果从MyObj中省略Lombok的@Data注解,则上面的代码将按原样工作,因为然后MyObj.equals方法会默认为对象引用比较,使myObjFirst不等于myObjSecond

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

英文:

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.

huangapple
  • 本文由 发表于 2020年8月1日 00:57:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/63196068.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定