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

Java 如何解决不必要的存根异常,java,junit,mockito,Java,Junit,Mockito,我的代码如下: @RunWith(MockitoJUnitRunner.class) public class MyClass { private static final String code ="Test"; @Mock private MyClassDAO dao; @InjectMocks private MyClassService Service = new MyClassServiceImpl(); @Test

我的代码如下:

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

      }
}
请帮我解决这个问题

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

此处的
when
将您的模拟配置为执行某些操作。但是,在这一行之后,您将不再以任何方式使用此模拟(除了执行
验证
)。Mockito警告您,
when
行因此毫无意义。也许您犯了一个逻辑错误?

@RunWith(MockitoJUnitRunner.class)
替换为
@RunWith(MockitoJUnitRunner.Silent.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();

首先,您应该检查您的测试逻辑。通常有3例。首先,你们在模仿错误的方法(你们输入了一个拼写错误,或者有人修改了测试代码,使模仿的方法不再被使用)。其次,在调用此方法之前,测试失败。第三,如果/switch分支在代码中的某个地方,那么您的逻辑就会出错,这样就不会调用mock方法

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

@RunWith(MockitoJUnitRunner.Silent.class) 
注释,或者如果您使用的是规则方法

@Rule
public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.LENIENT);
或者(同样的行为)

对于JUnit5测试,您可以使用
mockito JUnit jupiter
包中提供的注释使此异常静音

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

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

不必要的存根是在测试执行期间从未实现的存根方法调用(另请参见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
 ...
//测试中的代码:
...
字符串结果=translator.translate(“一”)
...
//测试:
...

when(translator.translate(“一”))。然后返回(“杰登”);// 对我来说,
@Rule
@RunWith(MockitoJUnitRunner.Silent.class)
建议都不起作用。这是一个遗留项目,我们在其中升级到mockito core 2.23.0

我们可以使用以下方法消除
不必要的stubbing异常

Mockito.lenient().when(mockedService.getUserById(any())).thenReturn(new User());
而不是:

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

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

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


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

当我尝试在间谍对象上使用
when
方法时,我遇到了
不必要的stubbingException
Mockito.lenient()
使异常静音,但测试结果不正确

对于间谍对象,必须直接调用这些方法

@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错误告诉我在
when
where
存根之后调用实际方法。因为我们没有调用刚才模拟的条件,所以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)
}
它正在工作

如果在模拟时使用any(),则必须使用重新调整
@RunWith(MockitoJUnitRunner.class)

@RunWith(MockitoJUnitRunner.Silent.class)
替换

@RunWith(MockitoJUnitRunner.class)

@RunWith(MockitoJUnitRunner.Silent.class)

删除
@RunWith(MockitoJUnitRunner.class)


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

中已经指出了这一点,但我认为这很容易被忽视:如果您只是简单地将JUnit 4测试类转换为JUnit 5测试类,用每个
@之前的
替换现有的@之前的,您可能会遇到一个不必要的StubbingException,如果您在该设置方法中执行了一些stubing,但至少有一个测试用例没有实现

关于这一点有更多的信息,基本上在每个之前的
@和之前的
@之间的测试执行有细微的区别。使用
@之前的
,如果任何测试用例实现存根就足够了,使用
@之前的每个
,所有用例都必须实现存根


如果您不想将
@beforeach
的设置分成许多小块(正如上面引用的评论正确指出的那样),还有另一个选项可以代替为整个测试类激活lenient模式:您只需使用
lenient()

分别在
@beforeach
方法中对存根进行lenient,正如其他人指出的,通常最简单的方法是删除不必要地存根方法调用的行

在我的例子中,它是在每个
之前的一个
@中,并且大部分时间都是相关的。在唯一未使用该方法的测试中,我重置了模拟,例如:

myMock.reset()
希望这能帮助其他有同样问题的人

(请注意,如果在同一个mock上有多个mock调用,那么这也会很不方便,因为除了
@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)
}
@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)
}
myMock.reset()