我在 DAO 和域类中有一个加载方法和一个保存方法。在我的应用程序中,我加载了一个域对象,更新了属性值并再次保存了该对象。我为应用程序编写了这个测试:
import ...
@RunWith(MockitoJUnitRunner.class)
public class MyAppTest
{
@Mock
private MyDao myDao;
@InjectMocks
private MyApp myApp;
@Test
public void testMyMethod(){
// given
MyDomainClass myObj = new MyDomainClass();
when(myDao.load(anyInt())).thenReturn(myObj);
// when
myApp.myMethod();
// then
ArgumentCaptor<MyDomainClass> argumentCaptor = ArgumentCaptor.forClass(MyDomainClass.class);
verify(myDao).save(argumentCaptor.capture());
assertThat(argumentCaptor.getValue().myInt, is(1));
}
}
尽管逻辑包含错误,但测试通过了:
public class MyApp
{
private MyDao myDao = new MyDao();
public void myMethod(){
MyDomainClass myObj = myDao.load(0);
myDao.save(myObj);
System.out.println("Saved myObj with myInt=" + myObj.myInt); // myInt=0
myObj.myInt = 1;
}
}
我的问题是是否有其他人遇到过这种情况,什么(junit)测试策略会暴露该错误。
编辑:为了响应第一个建议的解决方案,我像这样替换了参数捕获器:
...
@Test
public void testMyMethod(){
...
MyDomainClass expected = new MyDomainClass();
expected.myInt = 1;
verify(myDao).save(eq(expected));
}
测试仍然通过。原因是一样的:在调用 verify
时,myInt 的值在两个实例中都是 1
。我正在尝试验证 save
是在 myObj
的 myInt
设置为 0
的情况下调用的,即此测试应该失败。
我的问题也是是否有一般的事情,例如我应该考虑的不同测试策略。在某些情况下,ArgumentCaptors 是一个糟糕的选择吗?我知道我可以改为编写集成测试,但我认为这应该可以在单元测试中进行测试。
最佳答案
调用时...
assertThat(argumentCaptor.getValue().myInt, is(1));
... myInt
的值 是 1
因为它在 myApp.myMethod(); 中设置为该值;
在验证之前调用。
使用答案
如果你想在调用 myDao.save()
时针对 MyDomainClass
的状态进行断言 那么你需要通过“何时”阶段的回答来捕捉该状态。
由于 Mockito 记录了对传递给 myDao.save()
的 MyDomainClass
实例的引用(而不是它的副本),您可以使用 Answer
记录您自己的副本,然后针对该副本进行断言。
例如:
@Test
public void testMyMethod(){
// given
MyDomainClass myObj = new MyDomainClass();
when(myDao.load(anyInt())).thenReturn(myObj);
AtomicReference<MyDomainClass> actualSavedInstance = new AtomicReference<>();
doAnswer(invocation -> {
MyDomainClass supplied = (MyDomainClass) invocation.getArguments()[0];
// copy/clone state from supplied - which represents the instance passed to save - into a separate
// instance which can be used for an assertion
MyDomainClass actual = new MyDomainClass();
actual.myInt = supplied.myInt;
actualSavedInstance.set(actual);
return null;
}).when(myDao).save(myObj);
// when
myApp.myMethod();
assertThat(actualSavedInstance.get().myInt, is(0));
}
另一种选择
替代方法可能是将 MyDao.save()
中的“更新后增量”委托(delegate)给单独的参与者,然后模拟该参与者并验证它。
例如,将MyApp
改成如下:
public void myMethod(){
MyDomainClass myObj = myDao.load(0);
myDao.save(myObj);
System.out.println("Saved myObj with myInt=" + myObj.myInt); // myInt=0
// the handler will do this: myObj.myInt = 1;
postSaveHandler.handle(myObj);
}
然后像这样实现测试:
@RunWith(MockitoJUnitRunner.class)
public class MyAppTest
{
@Mock
private MyDao myDao;
@Mock
private PostSaveHandler postSaveHandler;
@InjectMocks
private MyApp myApp;
@Test
public void testMyMethod(){
// given
MyDomainClass myObj = new MyDomainClass();
when(myDao.load(anyInt())).thenReturn(myObj);
// when
myApp.myMethod();
ArgumentCaptor<MyDomainClass> argumentCaptor = ArgumentCaptor.forClass(MyDomainClass.class);
verify(myDao).save(argumentCaptor.capture());
assertThat(argumentCaptor.getValue().myInt, is(0));
verify(postSaveHandler).handle(myObj);
}
}
关于java - 在调用验证方法后更改值属性时,ArgumentCaptor.getValue() 上的 assertThat 误报,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48278935/