Mockito 在更改行为时调用模拟方法一次

Mockito calls mocked method once time upon chanching the behavior

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

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());                      
}
14:18:17.336 [main] INFO com.Foo - MOCK - waiting (1): FOO_1
14:18:17.343 [main] INFO com.Foo - MOCK - returning (1)
14:18:17.349 [main] INFO com.Foo - Result (1): BAR_1

只要使用 bar.string("FOO_1") 显然调用了一次该方法,测试就会通过。只要我在此执行后添加模拟 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 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());                      
}
14:19:31.603 [main] INFO com.Foo - MOCK - waiting (1): FOO_1
14:19:31.612 [main] INFO com.Foo - MOCK - returning (1)
14:19:31.620 [main] INFO com.Foo - Result (1): BAR_1
14:19:31.621 [main] INFO com.Foo - MOCK - waiting (1): 
14:19:31.621 [main] INFO com.Foo - MOCK - returning (1)
14:19:31.623 [main] INFO com.Foo - MOCK - returning (2): FOO_2
14:19:31.624 [main] INFO com.Foo - Result (2): BAR_2

令人惊讶的是,日志显示在第 4 行调用了没有参数的模拟方法。

当我在同一测试中包含更多此代码块 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) 调用,之前被模拟以增加AtomicInteger

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

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

改造后,由于新的mock生效,数字不再递增。您应该使用 clean mocks 以避免编写脆弱或复杂的测试代码。