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

huangapple go评论40阅读模式
英文:

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);

    // 模拟带有递增
    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";
    });
    // 调用递增
    log.info("Result (1): " + bar.string("FOO_1"));                       

    // 通过
    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);

    // 模拟带有递增
    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";
    });

    // 调用递增
    log.info("Result (1): " + bar.string("FOO_1"));

    /* 新代码块开始 */

    // 模拟不带递增
    Mockito.when(bar.string(Mockito.anyString())).then(invocation -> {    
         log.info("MOCK - returning (2): {}", invocation.getArguments()[0]);
         return "BAR_2";
    });

    // 调用不带递增
    // 前面的代码确实改变了模拟,但它被调用了一次以上
    log.info("Result (2): " + bar.string("FOO_2"));

    /* 新代码块结束 */

    // 失败,应该是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行被调用时没有参数。

当我在同一个测试中多次包含此代码块时,行为不会改变。测试始终失败,期望递增为 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 在测试期间改变模拟行为后精确调用模拟参数一次?

英文:

I encountered to a very strange behavior in case I change the mock behavior during the test. I mock a very simple interface:

interface Bar { 
    String string(String str); 
}

@Mock
private Bar bar;

Then I call it and count the number of invocations using AtomicInteger, which serves as a side-effect for this minimal working example.

@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

The test passes as long as the method was obviously invoked once using bar.string("FOO_1"). As long as I add the new behavior of the mock bar after this executes to not increment the AtomicInteger, there happens one more invocation of the original mock that should not be called:

@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

Surprisingly, the log shows the mocked method was called with no parameter on the 4th line.

The behavior doesn't change when I include more of this block of code within the same test N-times. The test always fails expecting the increment to be 2 instead of 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"));                       

What makes Mockito call exactly one time the mocked method with mocked parameters after its behavior is changed during the test 1+ times?

答案1

得分: 1

调用 bar.string(Mockito.anyString()) 在重新模拟操作中仍然等同于调用 bar.string(String),此前已模拟以增加 AtomicInteger

log.info("Result (1): " + bar.string("FOO_1")); // 增加到 1

Mockito.when(bar.string(Mockito.anyString())).then(invocation -> { // 增加到 2

重新模拟后,数字不再递增,因为新的模拟已生效。您应该使用干净的模拟,以避免编写脆弱或复杂的测试代码。

英文:

Calling bar.string(Mockito.anyString()) in the remocking operation is still equivalent to a bar.string(String) call, which was mocked earlier to increase the AtomicInteger.

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

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

After the remocking, the number is no longer incremented as the new mock has taken effect. You should work with clean mocks to avoid writing brittle or complex test code.

huangapple
  • 本文由 发表于 2020年7月22日 20:30:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/63034242.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定