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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/52139619/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 00:20:43  来源:igfitidea点击:

Simulation of Service using Mockito 2 leads to stubbing error

javaunit-testingmockingmockitojunit5

提问by aschoerk

I try to simulate the behaviour of a class, using Mockito. This worked using Mockito 1.x. Migrating to JUnit 5 and Mockito 2 it seems not to work anymore.

我尝试使用 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));
  }
}

The expectation is, that the mocked TestClass shows the behaviour as tested in the test-method.

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

The error I get is:

我得到的错误是:

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.

In both cases, the argument falseseems to be matched, though I clearly matched with true.

在这两种情况下,参数false似乎都匹配,尽管我明确匹配true.

Is that a bug in Mockito 2.17 or a misunderstanding. How should/can I use Mockito 2.x to simulate calls with different boolean arguments?

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

The examplecan also be found on github. But surefire will start the test only using

示例也可以在 github 上找到。但是surefire只会使用

mvn test -Dtest=MockitoExample

Executing the test using Mockito 2.21 leads to the same results.

使用 Mockito 2.21 执行测试会得到相同的结果。

采纳答案by aschoerk

Since Mockito 2.20 it is also possible, to add lenient() locally

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

回答by Mureinik

With strict stubs (the default behavior of Mockito) calling several whens on the same method will reset that mock. The solution is to call whenonceand have the logic in an Answer:

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

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

Alternatively, you can use lenient mocking, but that's not always a good idea - lenient mocking allows redundant stubbing, and makes it easier for you to make mistakes in your test, which may lead to unnoticed bugs in the "production" code:

或者,您可以使用 lenient mocking,但这并不总是一个好主意 - lenient mocking 允许冗余存根,并使您更容易在测试中犯错误,这可能会导致“生产”代码中未被注意的错误:

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class MockitoExample {

回答by johanneslink

Since the first answer came as a surprise, I checked the following:

由于第一个答案出乎意料,我检查了以下内容:

interface Poops {
    String get(boolean is);
}

@Test
void test1() {
    Poops a = mock(Poops.class);

    when(a.get(eq(true))).thenReturn("1");
    when(a.get(eq(false))).thenReturn("2");

    Assertions.assertEquals("1", a.get(true));
    Assertions.assertEquals("2", a.get(false));
}

It works with Mockito 2.21.0.

它适用于 Mockito 2.21.0。

Update: The problem seems to be the Jupiter Mockito extension which changes the default setting to Strictness.STRICT_STUBS.

更新:问题似乎是 Jupiter Mockito 扩展将默认设置更改为Strictness.STRICT_STUBS.

回答by davidxxx

Mockito 1 and 2 don't have the same "strictness" level.
Besides by using Mockito 2 with JUnit 4 or 5 the default level will be still different.

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

To sum up :

总结 :

3 levels of strictness :

3个级别的严格性:

  • LENIENT: minimum strictness
  • WARN: extra warnings emitted to the console
  • STRICT_STUBS: ensures clean tests by throwing exception if potential misuse but may also produce some false positives.
  • LENIENT: 最低限度的严格
  • WARN: 向控制台发出额外警告
  • STRICT_STUBS:通过在潜在误用时抛出异常来确保干净的测试,但也可能产生一些误报。

Default effective level according to the APIs used :

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

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

More details

更多细节

The actual Mockito documentation is very clear about that :

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

The Strictnessjavadocstates :

Strictnessjavadoc的状态:

Configures the "strictness" of Mockito during a mocking session.A session typically maps to a single test method invocation. Strictness drives cleaner tests and better productivity.The easiest way to leverage enhanced Strictness is usingMockito's JUnit support (MockitoRule or MockitoJUnitRunner).If you cannot use JUnit support MockitoSession is the way to go.

How strictness level influences the behavior of the test (mocking session)?

1.Strictness.LENIENT- no added behavior.The default of Mockito 1.x.Recommended only if you cannot use STRICT_STUBS nor WARN.

2.Strictness.WARN- helps keeping tests clean and improves debuggability.Reports console warnings about unused stubsand stubbing argument mismatch (see org.mockito.quality.MockitoHint).The default behavior of Mockito 2.x when JUnitRule or MockitoJUnitRunner are used. Recommended if you cannot use STRICT_STUBS.

3.Strictness.STRICT_STUBS- ensures clean tests, reduces test code duplication, improves debuggability.Best combination of flexibility and productivity. Highly recommended.Planned as default for Mockito v3.See STRICT_STUBS for the details.

在模拟会话期间配置 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。

But whatever the thrown exception associated to the message

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

"has following stubbing(s) with different arguments"

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

seems to be a excessively strict check. The exception message proves that in a some way :

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

However, there are legit scenarios when this exception generates false negative signal:

...

  • stubbed method is intentionally invoked with different arguments by code under test

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

...

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

So forbidding it by default seems to be too much.
So if you use JUnit 5, as alternative to STRICT_STUBSyou could use WARNINGbut you generally want to avoid LENIENTthat is too quiet.

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

In addition to MockitoExtension, the mockito-junit-jupiterlibrary provides @MockitoSettingsthat may be used at the method level as well as at the class level.

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

Here is an example :

这是一个例子:

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

    }

}

fooKo()throws the misuse Mockito exception while foo()is successful but provides helpful warnings :

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

[MockitoHint] FooTest (see javadoc for MockitoHint):
[MockitoHint] 1. Unused -> at FooTest.foo(FooTest.java:19)
[MockitoHint] 2. Unused -> at FooTest.foo(FooTest.java:21)

As other alternative you can also use Mockito.lenient()very well described by aschoerk to apply the lenient strictness for a specific invocation. As well as you can set every mock invocations as lenient at the mock instantiation :

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

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