使用Mockito 2模拟服务会导致存根错误

asc*_*erk 13 java unit-testing mocking mockito junit5

我尝试使用Mockito来模拟一个类的行为.这使用了Mockito 1.x. 迁移到JUnit 5和Mockito 2似乎不再起作用了.

@ExtendWith(MockitoExtension.class)
public class MockitoExample {

  static abstract class TestClass {
    public abstract int booleanMethod(boolean arg);
  }

  @Mock
  TestClass testClass;

  @BeforeEach
  public void beforeEach() {
    when(testClass.booleanMethod(eq(true))).thenReturn(1);
    when(testClass.booleanMethod(eq(false))).thenReturn(2);
  }

  @Test
  public void test() {
    assertEquals(1,testClass.booleanMethod(true));
    assertEquals(2,testClass.booleanMethod(false));
  }
}
Run Code Online (Sandbox Code Playgroud)

期望的是,模拟的TestClass显示了在测试方法中测试的行为.

我得到的错误是:

org.mockito.exceptions.misusing.PotentialStubbingProblem: 

  Strict stubbing argument mismatch. Please check:
   - this invocation of 'booleanMethod' method:
      testClass.booleanMethod(false);
      -> at org.oneandone.ejbcdiunit.mockito_example.MockitoExample.beforeEach(MockitoExample.java:30)
   - has following stubbing(s) with different arguments:
      1. testClass.booleanMethod(false);
        -> at org.oneandone.ejbcdiunit.mockito_example.MockitoExample.beforeEach(MockitoExample.java:29)
  Typically, stubbing argument mismatch indicates user mistake when writing tests.
  Mockito fails early so that you can debug potential problem easily.
  However, there are legit scenarios when this exception generates false negative signal:
    - stubbing the same method multiple times using 'given().will()' or 'when().then()' API
      Please use 'will().given()' or 'doReturn().when()' API for stubbing.
    - stubbed method is intentionally invoked with different arguments by code under test
      Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
  For more information see javadoc for PotentialStubbingProblem class.
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,这个论点false似乎是匹配的,尽管我很清楚地匹配true.

这是Mockito 2.17中的一个错误还是一个误解.我应该如何/可以使用Mockito 2.x来模拟具有不同布尔参数的调用?

示例也可以在github上找到.但是,surefire将仅使用启动测试

mvn test -Dtest=MockitoExample
Run Code Online (Sandbox Code Playgroud)

使用Mockito 2.21执行测试会得出相同的结果.

Mur*_*nik 13

使用严格的存根(Mockito的默认行为)when在同一方法上调用几个s将重置该模拟.解决方案是调用when 一次并将逻辑放在Answer:

@BeforeEach
public void beforeEach() {
    when(testClass.booleanMethod(anyBoolean())).thenAnswer(invocationOnMock -> {
        if ((boolean) invocationOnMock.getArguments()[0]) {
            return 1;
        }
        return 2;
    });
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用宽松的模拟,但这并不总是一个好主意 - 宽松的模拟允许冗余存根,并使您更容易在测试中出错,这可能导致"生产"代码中未被注意的错误:

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class MockitoExample {
Run Code Online (Sandbox Code Playgroud)

  • 真?我必须使用Answers,解释其中最初实现非常精细的DSL的参数,并使存根更容易出错?在这种情况下,放弃模拟使用并注入真实模拟似乎更好(至少,如果你使用弹簧或焊接) (4认同)
  • 如果您可以接受“doReturn().when()”语法,那么移植到该语法是一个不错的方法。 (2认同)

dav*_*xxx 13

Mockito 1 和 2 没有相同的“严格”级别。
除了将 Mockito 2 与 JUnit 4 或 5 一起使用外,默认级别仍然不同。

总结 :

3个级别的严格性:

  • LENIENT : 最低限度的严格
  • WARN : 向控制台发出额外警告
  • STRICT_STUBS :通过在潜在误用时抛出异常来确保干净的测试,但也可能产生一些误报。

根据使用的 API 的默认有效级别:

  • 莫基托 1 : LENIENT
  • 带有 JUnit 4 的 Mockito 2: WARN
  • 带有 JUnit 5 ( MockitoExtension.class) 的Mockito 2 :STRICT_STUBS
  • Mockito 3:计划是STRICT_STUBS

更多细节

实际的 Mockito 文档对此非常清楚:

Strictnessjavadoc的状态:

在模拟会话期间配置 Mockito 的“严格性”。会话通常映射到单个测试方法调用。Strictness 驱动更干净的测试和更好的生产力。利用增强 Strictness 的最简单方法是使用 Mockito 的 JUnit 支持(MockitoRule 或 MockitoJUnitRunner)。如果您不能使用 JUnit 支持 MockitoSession 是要走的路。

严格程度如何影响测试的行为(模拟会话)?

1. Strictness.LENIENT- 没有添加行为。Mockito 1.x 的默认值。仅当您不能使用 STRICT_STUBS 或 WARN 时才推荐。

2. Strictness.WARN- 有助于保持测试清洁并提高可调试性。报告有关未使用的存根和存根参数不匹配的控制台警告(请参阅 org.mockito.quality.MockitoHint)。使用 JUnitRule 或 MockitoJUnitRunner 时 Mockito 2.x 的默认行为。如果您不能使用 STRICT_STUBS,则推荐使用。

3. Strictness.STRICT_STUBS- 确保干净的测试,减少测试代码重复,提高可调试性。灵活性和生产力的最佳组合。强烈推荐。计划为 Mockito v3 的默认值。有关详细信息,请参阅 STRICT_STUBS。

但是无论抛出的与消息相关的异常

“具有以下不同参数的存根”

似乎是一个过于严格的检查。异常消息在某种程度上证明了这一点:

但是,当此异常生成假阴性信号时,存在合法情况:

...

  • 被测试的代码故意使用不同的参数调用存根方法

所以默认禁止它似乎太多了。
因此,如果您使用 JUnit 5,作为STRICT_STUBS您可以使用的替代品,WARNING但您通常希望避免LENIENT它太安静。

此外MockitoExtension,该mockito-junit-jupiter库还提供 @MockitoSettings可在方法级别和类级别使用的 。

这是一个例子:

import java.util.List;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

@ExtendWith(MockitoExtension.class)
public class FooTest {

    @MockitoSettings(strictness = Strictness.WARN)
    @Test
    void foo() throws Exception {
        List<String> strings = Mockito.mock(List.class);
        Mockito.when(strings.add("a"))
               .thenReturn(true);
        Mockito.when(strings.add("b"))
               .thenReturn(false);
    }

    @Test
    void fooKo() throws Exception {
        List<String> strings = Mockito.mock(List.class);
        Mockito.when(strings.add("a"))
               .thenReturn(true);
        Mockito.when(strings.add("b"))
               .thenReturn(false);

    }

}
Run Code Online (Sandbox Code Playgroud)

fooKo()foo()成功时抛出误用 Mockito 异常但提供有用的警告:

[MockitoHint] FooTest(参见 MockitoHint 的 javadoc):
[MockitoHint] 1. 未使用 -> 在 FooTest.foo(FooTest.java:19)
[MockitoHint] 2. 未使用 -> 在 FooTest.foo(FooTest.java:21)

作为另一种选择,您还可以使用Mockito.lenient()aschoerk 很好地描述来对特定调用应用宽松的严格性。您还可以在模拟实例化时将每个模拟调用设置为宽松:

@Test
void foo() throws Exception {
    List<String> strings = Mockito.mock(List.class, Mockito.withSettings()
                                                           .lenient());
     ....
}
Run Code Online (Sandbox Code Playgroud)


asc*_*erk 9

从Mockito 2.20开始,也可以在本地添加lenient()

@ExtendWith(MockitoExtension.class)
public class MockitoExample {

  static abstract class TestClass {
    public abstract int booleanMethod(boolean arg);
  }

  @Mock
  TestClass testClass;

  @BeforeEach
  public void beforeEach() {
    lenient().when(testClass.booleanMethod(eq(true))).thenReturn(1);
    lenient().when(testClass.booleanMethod(eq(false))).thenReturn(2);
  }

  @Test
  public void test() {
    assertEquals(1,testClass.booleanMethod(true));
    assertEquals(2,testClass.booleanMethod(false));
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我也同意这应该被接受,也许需要一些解释为什么需要它 (2认同)