英文:
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
实现,它通过它们的字段id
和someProperty
来比较对象。因为myObjFirst
和myObjSecond
上的两个字段都是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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论