java - 在调用验证方法后更改值属性时,ArgumentCaptor.getValue() 上的 assertThat 误报

标签 java unit-testing junit mockito

我在 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 是在 myObjmyInt 设置为 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/

相关文章:

Java 将 unix 时间转换为日期在每台计算机上不同,为什么?

python - 在 python 中模拟整个模块

javascript - 如何测试异步函数抛出的嵌套错误?

java - 为随机数生成器编写 JUnit 测试

junit - 我可以使用哪种断言方法来检查数组中的元素是否唯一?

java - 单元测试 Apache-Camel JmsReplyTo 路由流程

java - 将字 "one"更改为数字 "1"等等

java - 警告对话框中 EditText 框的空验证 - Android

java - 根据现有代码生成代码

java - 使用 JMockit 模拟静态类字段