如何解决不必要的存根异常


100

我的代码如下

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

请帮我解决

Answers:


120

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


44
欢迎。更新您的答案以解释为什么他们的OP应该替换此类代码非常值得。这将帮助他们和未来的访客了解。
Bug,

5
顺便说一句,这是@RunWith(MockitoJUnitRunner.Silent.class)不是 SILENT
fgysin起用莫妮卡

6
在科特林:@RunWith(MockitoJUnitRunner.Silent::class)
Juan Saravia

9
不知道为什么这个答案会不断增加而不加解释。其他答案更加有意义和准确。
Yogesh

8
这不能解决问题,而只是消除错误消息,并且还会影响该类中的所有其他测试(如果有)。
击剑者

98

首先,您应该检查测试逻辑。通常有3种情况。首先,您在模拟错误的方法(您输入了错误或有人更改了测试代码,因此不再使用模拟的方法)。其次,在调用此方法之前,您的测试失败。第三,如果if / switch分支在代码中的某个位置,则逻辑将出错,因此不会调用模拟方法。

如果是第一种情况,您总是想更改代码中使用的模拟方法。第二和第三取决于它。通常,如果此模拟没有用,则应删除该模拟。但是,有时在参数化测试中会出现某些情况,这些情况应该采用这种不同的方法,或者更早失效。然后,您可以将此测试分为两个或多个单独的测试,但这并不总是好看的。具有3个参数提供程序的3种测试方法可以使您的测试看起来不可读。在这种情况下,对于JUnit 4,您可以使用以下任一方法使此异常保持沉默

@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 {
}

3
@MockitoSettings(strictness = Strictness.LENIENT)是在我的设置中调整严格性的最简单方法。谢谢!
马特

4
该答案很好地概述了各种可能性。但是,您也可以使用逐个案例设置宽严Mockito.lenient().when(...)。对于这个特定问题,它将是Mockito.lenient().when(dao.doSearch(dto)).thenReturn(inspectionsSummaryList);
neXus

处理测试层次结构时,请在父类中定义ExtendWith,在子类中定义MockitoSettings。希望这可以节省一些人的时间,但我会为此付出代价。
miracle_the_V

32

沉默不是解决方案。您需要在测试中修复模拟。请参阅此处的官方文档。

不必要的存根是在测试执行期间从未实现的存根方法调用(另请参见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。


13
在许多情况下,您针对类似的@BeforeEach设置编写8-9个测试,其中由于少数测试的业务逻辑,一个存根返回的项目未使用。您可以(A)将其分解为多个测试,然后有效地复制/粘贴\ @BeforeEach部分减去一个项目(B)复制/粘贴Mockito即将执行的单行代码到使用它的6个测试中并将其粘贴不在2个中不这样做或(C)使用无声。我更喜欢使用静音/警告。这不是一个坏的测试。
RockMeetHardplace

1
@ RockMeetHardplace,Silent不是解决方案,很快您会看到较少的复制/粘贴,但是当新人在您的项目上维护测试时,这将是有问题的。如果Mockito书店做到了,那就不是没有。
斯特凡·格兰隆

2
@sgrillon:但是此系统检测到大量假阳性。也就是说,它说有些东西未使用,但显然不是,因为删除存根会破坏执行。并不是说测试代码不能得到改进,而是重要的存根线永远不会被检测为“不必要的”。因此,能够禁用此检查的重要性非常迫切。
卡里根

@Carighan,如果检测到您的模拟不正确,则可能与您的想法不符。这可以在出现错误的情况下进行正常测试。
斯特凡·格林隆

@sgrillon,对不起,我从没再与您联系。事实证明,以前存在一个错误,根据测试执行顺序,它会生成“错误命中”,在一个测试中使用但在另一个测试中被覆盖的存根会触发该错误。据我所知,它早已固定。
卡里汉

27

对我而言@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());

不用说,您应该看一下测试代码,但是我们需要先编译好东西,然后首先运行测试;)


6
恕我直言。这是我在沉默整个测试类时发现的最有用的答案。
priyeshdkr

因为我只想抑制1次嘲笑,所以这对我来说是最好的答案。不过,这并不是OP的真正答案。
Hans Wouters

25
 when(dao.doSearch(dto)).thenReturn(inspectionsSummaryList);//got error in this line
 verify(dao).doSearchInspections(dto);

when这里配置您的模拟做一些事情。但是,在此行之后,您将不再使用该模拟程序(除了执行之外verify)。Mockito警告您,这when条线毫无意义。也许您犯了逻辑错误?


感谢您的帮助
VHS

我同时需要时,验证报表好心建议如何进一步移动
VHS

2
在您的测试类(Service)上调用一个函数,以查看其是否正确反应。您根本没有这样做,那么您在这里测试什么?
john16384

3

查看堆栈跟踪的一部分,就好像您在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();
    }
}

我宁愿考虑在必要时将测试重构为存根。


2

如果您改用这种风格:

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

替换为:

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

2

更换

@RunWith(MockitoJUnitRunner.class)

@RunWith(MockitoJUnitRunner.Silent.class)

删除@RunWith(MockitoJUnitRunner.class)

只是注释掉不需要的模拟调用(显示为未经授权的存根)。


1

UnnecessaryStubbingException当我尝试when在Spy对象上使用这些方法时,我遇到了。 Mockito.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);
    }
}

1

好吧,在我的情况下,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)
}

现在正在工作。



0

如果在模拟时使用any(),则必须将@RunWith(MockitoJUnitRunner.class)替换为@RunWith(MockitoJUnitRunner.Silent.class)。


0

当您创建一个模拟并且不使用该模拟时,它将引发未使用的存根异常。在您的情况下,该模拟实际上并未被调用。因此,它将引发该错误。因此,relpace @RunWith(MockitoJUnitRunner.class)@RunWith(MockitoJUnitRunner.Silent.class)这将消除错误。如果您仍然想使用,请@RunWith(MockitoJUnitRunner.class)尝试调试您的逻辑,无论您实际上模拟的函数是否被调用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.