Java Mockito在改变行为时调用mocked方法一次

Java Mockito在改变行为时调用mocked方法一次,java,mocking,mockito,junit5,Java,Mocking,Mockito,Junit5,我遇到了一个非常奇怪的行为,以防在测试期间更改模拟行为。我模拟了一个非常简单的界面: interface Bar { String string(String str); } @Mock private Bar bar; 然后我调用它并使用AtomicInteger计算调用次数,这是这个最小工作示例的一个副作用 @Test public void test() { AtomicInteger atomicInteger = new AtomicInteger(0);

我遇到了一个非常奇怪的行为,以防在测试期间更改模拟行为。我模拟了一个非常简单的界面:

interface Bar { 
    String string(String str); 
}

@Mock
private Bar bar;
然后我调用它并使用
AtomicInteger
计算调用次数,这是这个最小工作示例的一个副作用

@Test
public void test() {

    AtomicInteger atomicInteger = new AtomicInteger(0);

    // Mock with the increment
    Mockito.when(bar.string(Mockito.anyString())).then(invocation -> {    
        log.info("MOCK - waiting (1): {}", invocation.getArguments()[0]);
        atomicInteger.incrementAndGet();
        log.info("MOCK - returning (1)");
        return "BAR_1";
    });
    // Invocation of the increment
    log.info("Result (1): " + bar.string("FOO_1"));                       

    // Passes
    Assertions.assertEquals(1, atomicInteger.get());                      
}
只要使用
bar.string(“FOO_1”)
一次调用该方法,测试就会通过。只要我在执行后添加mock
bar
的新行为,以不增加
AtomicInteger
,就会再次调用不应调用的原始mock:

@Test
public void test() {

    AtomicInteger atomicInteger = new AtomicInteger(0);

    // Mock with the increment
    Mockito.when(bar.string(Mockito.anyString())).then(invocation -> {
        log.info("MOCK - waiting (1): {}", invocation.getArguments()[0]);
        atomicInteger.incrementAndGet();
        log.info("MOCK - returning (1)");
        return "BAR_1";
    });

    // Invocation with increment
    log.info("Result (1): " + bar.string("FOO_1"));

    /* NEW CODE BLOCK STARTS */

    // Mock without the increment
    Mockito.when(bar.string(Mockito.anyString())).then(invocation -> {    
         log.info("MOCK - returning (2): {}", invocation.getArguments()[0]);
         return "BAR_2";
    });

    // Invocation without the increment
    // The previous lines really changed the mock, but it was called one more times
    log.info("Result (2): " + bar.string("FOO_2"));

    /* NEW CODE BLOCK ENDS */

    // Fails, it is 2
    Assertions.assertEquals(1, atomicInteger.get());                      
}
令人惊讶的是,日志显示在第4行调用mock方法时没有参数

当我在同一测试
N
-次中包含更多这段代码时,行为不会改变。测试总是失败,期望增量为
2
,而不是
1

Mockito.when(bar.string(Mockito.anyString())).then(invocation -> {
     log.info("MOCK - returning (N): {}", invocation.getArguments()[0]);
     return "BAR_N";
});
log.info("Result (N): " + bar.string("FOO_N"));                       
是什么使得Mockito在其行为在测试1+次期间发生更改后,只调用一次带有模拟参数的模拟方法?

在remocking操作中调用
bar.string(Mockito.anyString())
仍然相当于调用
bar.string(string)
,它在前面被模拟以增加原子整数

log.info("Result (1): " + bar.string("FOO_1"));  // Increases to 1

Mockito.when(bar.string(Mockito.anyString())).then(invocation -> {  // Increases to 2

重新填充后,随着新模拟生效,该数字不再增加。您应该使用,以避免编写脆弱或复杂的测试代码。

您实际上不是在手动编写调用计数和参数检查,是吗?它们有现成的机制。@Kayaman:这是一个无关紧要的问题。我用一个最小的工作示例来演示Mockito在副作用方面的奇怪的behvaior。考虑<代码> AtomicInteger:将增量和获取< /代码>作为副作用方法。您正在移除一种方法来做一些不必要的事情。我需要验证你是否意识到你在做一些奇怪的事情,所以你也不应该对你得到的奇怪结果感到惊讶。最重要的是重置或重建您的mock,这一结果的原因是在内部实现中。我正在重新检查一个方法,以避免产生副作用(因此没有增量),并演示如何调用原始mock方法(使用增量),尽管在调用一次时只应调用一次。第二次调用发生在重新标记的方法上,没有副作用(没有增量)。实际上,最初的方法仍然以某种方式被再次调用。我知道我在做什么,这就是为什么我对它给我的结果感到惊讶。您如何解释第二个日志中的第四行?当您尝试重新修改方法时,为什么您认为
bar.string(“FOO_1”)
会增加
AtomicInteger
bar.string(Mockito.anyString())
?它们都调用了mock方法,从而增加了数量,这就是为什么您不应该尝试重新修改该方法的原因!这是一个方法调用,唯一肯定不会产生副作用的地方是一个空的mock,以前被mock的方法不是空的。我已经用
Mockito.when(..).then(..)then(..)
链解决了这个问题。
Mockito.when(bar.string(Mockito.anyString())).then(invocation -> {
     log.info("MOCK - returning (N): {}", invocation.getArguments()[0]);
     return "BAR_N";
});
log.info("Result (N): " + bar.string("FOO_N"));                       
log.info("Result (1): " + bar.string("FOO_1"));  // Increases to 1

Mockito.when(bar.string(Mockito.anyString())).then(invocation -> {  // Increases to 2