如何解决不必要的 Stubbing 异常

我的代码如下,

@RunWith(MockitoJUnitRunner.class)
public class MyClass {


private static final String code ="Test";


@Mock
private MyClassDAO dao;


@InjectMocks
private MyClassService Service = new MyClassServiceImpl();


@Test
public void testDoSearch() throws Exception {
final String METHOD_NAME = logger.getName().concat(".testDoSearchEcRcfInspections()");
CriteriaDTO dto = new CriteriaDTO();
dto.setCode(code);
inspectionService.searchEcRcfInspections(dto);
List<SearchCriteriaDTO> summaryList = new ArrayList<SearchCriteriaDTO>();
inspectionsSummaryList.add(dto);
when(dao.doSearch(dto)).thenReturn(inspectionsSummaryList);//got error in this line
verify(dao).doSearchInspections(dto);


}
}

我越来越低于例外

org.mockito.exceptions.misusing.UnnecessaryStubbingException:
Unnecessary stubbings detected in test class: Test
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
1. -> at service.Test.testDoSearch(Test.java:72)
Please remove unnecessary stubbings or use 'silent' option. More info: javadoc for UnnecessaryStubbingException class.
at org.mockito.internal.exceptions.Reporter.formatUnncessaryStubbingException(Reporter.java:838)
at org.mockito.internal.junit.UnnecessaryStubbingsReporter.validateUnusedStubs(UnnecessaryStubbingsReporter.java:34)
at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:49)
at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:103)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

请帮助我如何解决

253069 次浏览
 when(dao.doSearch(dto)).thenReturn(inspectionsSummaryList);//got error in this line
verify(dao).doSearchInspections(dto);

这里的 when将您的 mock 配置为执行某些操作。但是,在这一行之后,您不再以任何方式使用这个 mock (除了执行 verify之外)。Mockito 警告你,因此 when线是没有意义的。也许你的逻辑错误?

@RunWith(MockitoJUnitRunner.Silent.class)代替 @RunWith(MockitoJUnitRunner.class)

看看您的堆栈跟踪的一部分,它看起来像是您正在其他地方撞击 dao.doSearch()。更像是重复创建同一方法的存根。

Following stubbings are unnecessary (click to navigate to relevant line of code):
1. -> at service.Test.testDoSearch(Test.java:72)
Please remove unnecessary stubbings or use 'silent' option. More info: javadoc for UnnecessaryStubbingException class.

以下面的测试类为例:

@RunWith(MockitoJUnitRunner.class)
public class SomeTest {
@Mock
Service1 svc1Mock1;


@Mock
Service2 svc2Mock2;


@InjectMock
TestClass class;


//Assume you have many dependencies and you want to set up all the stubs
//in one place assuming that all your tests need these stubs.


//I know that any initialization code for the test can/should be in a
//@Before method. Lets assume there is another method just to create
//your stubs.


public void setUpRequiredStubs() {
when(svc1Mock1.someMethod(any(), any())).thenReturn(something));
when(svc2Mock2.someOtherMethod(any())).thenReturn(somethingElse);
}


@Test
public void methodUnderTest_StateUnderTest_ExpectedBehavior() {
// You forget that you defined the stub for svcMock1.someMethod or
//thought you could redefine it. Well you cannot. That's going to be
//a problem and would throw your UnnecessaryStubbingException.
when(svc1Mock1.someMethod(any(),any())).thenReturn(anyThing);//ERROR!
setUpRequiredStubs();
}
}

我宁愿考虑重构您的测试,以便在必要的地方存根。

如果你使用这种风格:

@Rule
public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);

取而代之的是:

@Rule
public MockitoRule rule = MockitoJUnit.rule().silent();

首先,您应该检查您的测试逻辑。通常有三个案子。首先,您模仿了错误的方法(您输入错误或者有人更改了测试代码,以便不再使用模仿的方法)。其次,在调用此方法之前测试失败。第三,如果/switch 分支位于代码中的某个位置,那么您的逻辑就错了,因此不会调用模拟方法。

如果这是第一种情况,您总是希望为代码中使用的方法更改模拟方法。第二个和第三个要看情况。通常情况下,如果模拟没有用处,您应该直接删除它。但是有时参数化测试中会出现某些情况,这些情况应该采用这种不同的路径,否则就会提前失败。然后你可以把这个测试分成两个或更多个独立的测试,但是这并不总是好看的。3个可能有3个参数提供程序的测试方法会使您的测试看起来不可读。在这种情况下,对于 JUnit4,可以使用

@RunWith(MockitoJUnitRunner.Silent.class)

注释,或者使用规则方法

@Rule
public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.LENIENT);

或(同样的行为)

@Rule
public MockitoRule rule = MockitoJUnit.rule().silent();

对于 JUnit 5测试,您可以使用 mockito-junit-jupiter包中提供的注释来禁止这个异常:

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class JUnit5MockitoTest {
}

沉默不是一个解决方案。 您需要在测试中修复您的 mock。

不必要的存根是在测试执行期间从未实现的存根方法调用(另见 MockitoHint) ,例如:

//code under test:
...
String result = translator.translate("one")
...


//test:
...
when(translator.translate("one")).thenReturn("jeden"); // <- stubbing realized during code execution
when(translator.translate("two")).thenReturn("dwa"); // <- stubbing never realized
...

请注意,在测试执行期间,在测试下的代码中从未实现一个存根方法。偏离的存根可能是开发人员的疏忽、复制粘贴的工件或者不理解测试/代码的结果。无论哪种方式,开发人员最终都会得到不必要的测试代码。为了保持代码库的清洁和可维护性,有必要删除不必要的代码。否则测试就很难阅读和推理。

要了解有关检测未使用的存根的更多信息,请参见 MockitoHint。

对我来说,无论是 @Rule还是 @RunWith(MockitoJUnitRunner.Silent.class)的建议都不起作用。这是一个遗留项目,我们升级到仿核心2.23.0。

我们可以通过以下方法摆脱 UnnecessaryStubbingException:

Mockito.lenient().when(mockedService.getUserById(any())).thenReturn(new User());

而不是:

when(mockedService.getUserById(any())).thenReturn(new User());

不用说,您应该更愿意查看测试代码,但是我们首先需要编译内容并运行测试;)

在大型项目的情况下,很难修复这些异常中的每一个。同时,不建议使用 Silent。我已经写了一个脚本,删除所有不必要的存根给出了一个清单。

Https://gist.github.com/cueo/da1ca49e92679ac49f808c7ef594e75b

我们只需要复制粘贴 mvn输出,并使用正则表达式编写这些异常的列表,然后让脚本处理其余的异常。

当我尝试在 Spy 对象上使用 when方法时,我得到了 UnnecessaryStubbingExceptionMockito.lenient()屏蔽了异常,但测试结果不正确。

对于 Spy 对象,必须直接调用方法。

@ExtendWith(MockitoExtension.class)
@RunWith(JUnitPlatform.class)
class ArithmTest {


@Spy
private Arithm arithm;


@Test
void testAddition() {


int res = arithm.add(2, 5);


// doReturn(7).when(arithm).add(2, 5);
assertEquals(res, 7);
}
}

在我的例子中,Mockito 错误告诉我在 whenwhenever存根之后调用实际的方法。由于我们没有调用我们刚才嘲笑的条件,Mockito 将其报告为不必要的存根或代码。

以下是出现错误时的情况:

@Test
fun `should return error when item list is empty for getStockAvailability`() {
doAnswer(
Answer<Void> { invocation ->
val callback =
invocation.arguments[1] as GetStockApiCallback<StockResultViewState.Idle, StockResultViewState.Error>
callback.onApiCallError(stockResultViewStateError)
null
}
).whenever(stockViewModelTest)
.getStockAvailability(listOf(), getStocksApiCallBack)
}

然后调用 when 语句中提到的实际方法来模拟该方法。

所做的修改如下 stockViewModelTest.getStockAvailability(listOf(), getStocksApiCallBack)

@Test
fun `should return error when item list is empty for getStockAvailability`() {
doAnswer(
Answer<Void> { invocation ->
val callback =
invocation.arguments[1] as GetStockApiCallback<StockResultViewState.Idle, StockResultViewState.Error>
callback.onApiCallError(stockResultViewStateError)
null
}
).whenever(stockViewModelTest)
.getStockAvailability(listOf(), getStocksApiCallBack)
//called the actual method here
stockViewModelTest.getStockAvailability(listOf(), getStocksApiCallBack)
}

现在起作用了。

如果在嘲笑时使用 any () ,则必须使用 @RunWith(MockitoJUnitRunner.Silent.class).

替换

@RunWith(MockitoJUnitRunner.class)

@RunWith(MockitoJUnitRunner.Silent.class)

移除 @RunWith(MockitoJUnitRunner.class)

只需注释掉不想要的嘲笑电话(显示为未经授权的存根)。

这一点在 此评论中已经指出,但是我认为这太容易被忽略: 如果你只是通过用 @BeforeEach替换一个现有的 @Before将一个 JUnit 4测试类转换成 JUnit 5测试类,并且如果你在设置方法中执行一些 至少一个测试用例没有实现的存根化操作,那么你可能会遇到一个 UnnecessaryStubbingException

这个 Mockito 线程 有更多关于这方面的信息,基本上在 @Before@BeforeEach之间的测试执行上有一个细微的差别。对于 @Before,如果 任何测试病例实现了结瘤就足够了,对于 @BeforeEach所有病例则必须实现结瘤。

如果你不想把 @BeforeEach的设置拆分成许多小块(正如上面引用的评论所指出的) ,还有另外一个选项可以替代激活整个测试类的宽松模式: 你只需要使用 lenient()使那些在 @BeforeEach方法中的碎块变得宽松。

正如其他人指出的那样,通常是删除不必要地阻塞方法调用的行的最简单方法。

在我的情况下,它是在一个 @BeforeEach和它是相关的大多数时间。在唯一没有使用该方法的测试中,我重置了 mock,例如:

myMock.reset()

希望这能帮助其他有同样问题的人。

(请注意,如果在同一模拟上有多个模拟调用,这可能也会造成不便,因为您将不得不模拟除了未调用的方法之外的所有其他方法。)