【问题标题】:How to resolve Unneccessary Stubbing exception如何解决不必要的存根异常
【发布时间】:2017-03-22 09:35:49
【问题描述】:

我的代码如下,

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

请帮我解决一下

【问题讨论】:

    标签: java junit mockito


    【解决方案1】:

    首先你应该检查你的测试逻辑。通常有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 {
    }
    

    【讨论】:

    • @MockitoSettings(strictness = Strictness.LENIENT) 是在我的设置中调整严格性的最简单方法。谢谢!
    • 这个答案很好地概述了可能性。但是,您也可以使用Mockito.lenient().when(...) 逐个案例设置宽松的严格性;对于这个特定的问题,它将是Mockito.lenient().when(dao.doSearch(dto)).thenReturn(inspectionsSummaryList);
    • 在处理测试层次结构时,在超类中定义 ExtendWith,在子类中定义 MockitoSettings。希望这可以为我的费用节省时间。
    【解决方案2】:

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

    【讨论】:

    • 欢迎。非常值得更新您的答案以解释 为什么 他们 OP 应该替换此类代码。这将有助于他们和未来的访客理解。
    • 顺便说一句,它是@RunWith(MockitoJUnitRunner.Silent.class) 并且不是 SILENT
    • 在 Kotlin 中:@RunWith(MockitoJUnitRunner.Silent::class)
    • 不知道为什么这个答案在没有解释的情况下不断得到支持。其他答案更有意义、更准确。
    • 这并不能解决问题,只是简单地抑制错误消息,并且还会影响类中的所有其他测试(如果有)。
    【解决方案3】:

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

    我们可以通过以下方式摆脱UnnecessaryStubbingException

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

    代替:

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

    毋庸置疑,您应该看一下测试代码,但我们需要先编译这些东西并运行测试;)

    【讨论】:

    • 恕我直言。这是我在这里找到的最有用的答案,而不是让整个测试类静音。
    • 因为我只想抑制 1 次嘲笑,所以这对我来说是最好的答案。不过,这并不是 OP 的真正答案。
    • 我不断收到无法解决“Mockito”中的“宽松”方法。有人知道为什么吗?
    【解决方案4】:

    静默不是解决方案。您需要在测试中修复您的模拟。参见官方文档here

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

    【讨论】:

    • 在许多情况下,您针对类似的 @BeforeEach 设置编写 8-9 个测试,其中由于少数测试的业务逻辑,从一个存根返回的项目未使用。您可以 (A) 将其分解为多个测试并有效地复制/粘贴 \@BeforeEach 部分减去一个项目 (B) 将 Mockito 是 emo 的单行复制/粘贴到使用它并拥有它的 6 个测试中not in the 2 that don't or (C) 使用沉默。我更喜欢使用静默/警告。这不是一个失败的测试。
    • @RockMeetHardplace,静默不是解决方案,很快您就会看到较少的复制/粘贴,但是当您的项目由新人维护您的测试时,这将是一个问题。如果 Mockito 书店这样做,那不是白费。
    • @sgrillon :但是这个系统会检测到大量的误报。也就是说,它说有些东西是未使用的,但显然不是,因为删除存根会破坏执行。并不是说测试代码无法改进,而是一条重要的存根线应该永远被检测为“不必要的”。因此,能够禁用此检查非常重要。
    • @Carighan,如果您的模拟被检测为不正确,那可能不是您想的那样。这会在可能存在错误的情况下为您提供 OK 测试。
    • @sgrillon,对不起,我从来没有回复过你。事实证明,曾经有一个错误,根据测试执行顺序,它会生成“错误命中”,其中在一个测试中使用但在另一个测试中被覆盖的存根会触发它。不过据我所知,它早已修复。
    【解决方案5】:
     when(dao.doSearch(dto)).thenReturn(inspectionsSummaryList);//got error in this line
     verify(dao).doSearchInspections(dto);
    

    这里的when 配置你的模拟来做一些事情。但是,在此行之后,您不再以任何方式使用此模拟(除了执行verify)。 Mockito 警告您 when 行因此毫无意义。也许你犯了一个逻辑错误?

    【讨论】:

    • 感谢您的帮助
    • 我需要 when 和 verify 声明请建议如何进一步
    • 在您的测试类 (Service) 上调用一个函数,看看它是否反应正确。你根本没有这样做,所以你在这里测试什么?
    【解决方案6】:

    替换

    @RunWith(MockitoJUnitRunner.class)

    @RunWith(MockitoJUnitRunner.Silent.class)

    删除@RunWith(MockitoJUnitRunner.class)

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

    【讨论】:

      【解决方案7】:

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

      在我的情况下,它位于 @BeforeEach 中,并且大部分时间都是相关的。在唯一没有使用该方法的测试中,我重置了模拟,例如:

      myMock.reset()
      

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

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

      【讨论】:

      • 我喜欢这种方法。不过有一件事,我认为方法是reset(myMock),而不是myMock.reset()
      【解决方案8】:

      this comment 中已经指出了这一点,但我认为这太容易被忽视:如果您只是通过替换现有的 @ 将 JUnit 4 测试类转换为 JUnit 5 测试类,您可能会遇到UnnecessaryStubbingException 987654324@ 和 @BeforeEach,并且如果您在该设置方法中执行了一些 至少一个 测试用例未实现的存根。

      This Mockito thread 对此有更多信息,基本上@Before@BeforeEach 之间的测试执行存在细微差别。对于@Before,如果任何 个测试用例实现了存根就足够了,对于@BeforeEach所有 个案例都必须这样做。

      如果您不想将@BeforeEach 的设置分解成许多小块(正如上面引用的评论正确指出的那样),还有另一个选择,而不是为整个测试类激活宽松模式:您只能单独使用lenient() 使@BeforeEach 方法中的那些存根变得宽松。

      【讨论】:

        【解决方案9】:

        查看堆栈跟踪的一部分,您似乎在其他地方存根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();
            }
        }
        

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

        【讨论】:

          【解决方案10】:

          好吧,就我而言,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)
          }
          

          它现在正在工作。

          【讨论】:

            【解决方案11】:

            如果您改用这种样式:

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

            替换为:

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

            【讨论】:

              【解决方案12】:

              当我尝试在 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);
                  }
              }
              

              【讨论】:

                【解决方案13】:

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

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

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

                【讨论】:

                  【解决方案14】:

                  如果你在 mocking 时使用 any(),你必须用 relpace @RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.Silent.class).

                  【讨论】:

                  • 这完全是错误的。 any() 正确使用时与普通跑步者完美搭配。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-11-07
                  • 1970-01-01
                  • 2013-10-13
                  • 2013-06-14
                  • 2018-01-09
                  • 2017-09-15
                  • 2017-09-14
                  相关资源
                  最近更新 更多