使用 Mockito 2 模拟服务导致存根错误
Simulation of Service using Mockito 2 leads to stubbing error
我尝试使用 Mockito 模拟 class 的行为。
这使用 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));
}
}
预期是模拟的 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.
在这两种情况下,参数 false
似乎都匹配,尽管我清楚地匹配了 true
。
这是 Mockito 2.17 中的错误还是误解。 should/can 我如何使用 Mockito 2.x 来模拟具有不同布尔参数的调用?
example 也可以在 github 上找到。但 surefire 将仅使用
开始测试
mvn test -Dtest=MockitoExample
使用 Mockito 2.21 执行测试会导致相同的结果。
使用严格的存根(Mockito 的默认行为)在同一方法上调用多个 when
将重置该模拟。解决方案是调用 when
一次 并将逻辑放在 Answer
:
中
@BeforeEach
public void beforeEach() {
when(testClass.booleanMethod(anyBoolean())).thenAnswer(invocationOnMock -> {
if ((boolean) invocationOnMock.getArguments()[0]) {
return 1;
}
return 2;
});
}
或者,您可以使用宽松的模拟,但这并不总是一个好主意 - 宽松的模拟允许冗余存根,并使您更容易在测试中犯错误,这可能会导致 "production"代码:
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class MockitoExample {
由于第一个答案出人意料,我检查了以下内容:
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));
}
它适用于 Mockito 2.21.0。
更新:
问题似乎是 Jupiter Mockito 扩展将默认设置更改为 Strictness.STRICT_STUBS
。
从 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));
}
}
Mockito 1 和 2 的 "strictness" 级别不同。
除了将 Mockito 2 与 JUnit 4 或 5 一起使用之外,默认级别仍然会有所不同。
总结一下:
3 个严格级别:
LENIENT
:最低严格度
WARN
:向控制台发出额外警告
STRICT_STUBS
:如果可能存在误用,通过抛出异常来确保干净的测试,但也可能产生一些误报。
根据所用 API 的默认有效级别:
- 模拟 1:
LENIENT
- 带有 JUnit 4 的 Mockito 2:
WARN
- Mockito 2 与 JUnit 5 (
MockitoExtension.class
) : STRICT_STUBS
- Mockito 3:计划
STRICT_STUBS
。
更多详情
实际的 Mockito 文档对此非常清楚:
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.
但是无论抛出与消息关联的异常是什么
"has following stubbing(s) with different arguments"
似乎是一个过于严格的检查。
异常消息在某种程度上证明了这一点:
However, there are legit scenarios when this exception generates false
negative signal:
...
- stubbed method is intentionally invoked with different arguments by code under test
所以默认禁止它似乎太多了。
因此,如果您使用 JUnit 5,作为 STRICT_STUBS
的替代方案,您可以使用 WARNING
,但您通常希望避免过于安静的 LENIENT
。
除了MockitoExtension
,mockito-junit-jupiter
库还提供
@MockitoSettings
可以在方法级别和 class 级别使用。
这是一个例子:
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()
抛出误用 Mockito 异常,而 foo()
成功但提供有用的警告:
[MockitoHint] FooTest (see javadoc for MockitoHint):
[MockitoHint] 1. Unused -> at FooTest.foo(FooTest.java:19)
[MockitoHint] 2. Unused -> at FooTest.foo(FooTest.java:21)
作为其他选择,您还可以使用 Mockito.lenient()
很好地描述了
aschoerk 对特定调用应用宽松的严格性。
以及您可以在模拟实例化时将每个模拟调用设置为宽松:
@Test
void foo() throws Exception {
List<String> strings = Mockito.mock(List.class, Mockito.withSettings()
.lenient());
....
}
我尝试使用 Mockito 模拟 class 的行为。 这使用 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));
}
}
预期是模拟的 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.
在这两种情况下,参数 false
似乎都匹配,尽管我清楚地匹配了 true
。
这是 Mockito 2.17 中的错误还是误解。 should/can 我如何使用 Mockito 2.x 来模拟具有不同布尔参数的调用?
example 也可以在 github 上找到。但 surefire 将仅使用
开始测试mvn test -Dtest=MockitoExample
使用 Mockito 2.21 执行测试会导致相同的结果。
使用严格的存根(Mockito 的默认行为)在同一方法上调用多个 when
将重置该模拟。解决方案是调用 when
一次 并将逻辑放在 Answer
:
@BeforeEach
public void beforeEach() {
when(testClass.booleanMethod(anyBoolean())).thenAnswer(invocationOnMock -> {
if ((boolean) invocationOnMock.getArguments()[0]) {
return 1;
}
return 2;
});
}
或者,您可以使用宽松的模拟,但这并不总是一个好主意 - 宽松的模拟允许冗余存根,并使您更容易在测试中犯错误,这可能会导致 "production"代码:
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class MockitoExample {
由于第一个答案出人意料,我检查了以下内容:
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));
}
它适用于 Mockito 2.21.0。
更新:
问题似乎是 Jupiter Mockito 扩展将默认设置更改为 Strictness.STRICT_STUBS
。
从 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));
}
}
Mockito 1 和 2 的 "strictness" 级别不同。
除了将 Mockito 2 与 JUnit 4 或 5 一起使用之外,默认级别仍然会有所不同。
总结一下:
3 个严格级别:
LENIENT
:最低严格度WARN
:向控制台发出额外警告STRICT_STUBS
:如果可能存在误用,通过抛出异常来确保干净的测试,但也可能产生一些误报。
根据所用 API 的默认有效级别:
- 模拟 1:
LENIENT
- 带有 JUnit 4 的 Mockito 2:
WARN
- Mockito 2 与 JUnit 5 (
MockitoExtension.class
) :STRICT_STUBS
- Mockito 3:计划
STRICT_STUBS
。
更多详情
实际的 Mockito 文档对此非常清楚:
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.
但是无论抛出与消息关联的异常是什么
"has following stubbing(s) with different arguments"
似乎是一个过于严格的检查。 异常消息在某种程度上证明了这一点:
However, there are legit scenarios when this exception generates false negative signal:
...
- stubbed method is intentionally invoked with different arguments by code under test
所以默认禁止它似乎太多了。
因此,如果您使用 JUnit 5,作为 STRICT_STUBS
的替代方案,您可以使用 WARNING
,但您通常希望避免过于安静的 LENIENT
。
除了MockitoExtension
,mockito-junit-jupiter
库还提供
@MockitoSettings
可以在方法级别和 class 级别使用。
这是一个例子:
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()
抛出误用 Mockito 异常,而 foo()
成功但提供有用的警告:
[MockitoHint] FooTest (see javadoc for MockitoHint): [MockitoHint] 1. Unused -> at FooTest.foo(FooTest.java:19) [MockitoHint] 2. Unused -> at FooTest.foo(FooTest.java:21)
作为其他选择,您还可以使用 Mockito.lenient()
很好地描述了
aschoerk 对特定调用应用宽松的严格性。
以及您可以在模拟实例化时将每个模拟调用设置为宽松:
@Test
void foo() throws Exception {
List<String> strings = Mockito.mock(List.class, Mockito.withSettings()
.lenient());
....
}