【问题标题】:Using Mockito with multiple calls to the same method with the same arguments使用 Mockito 多次调用具有相同参数的相同方法
【发布时间】:2011-12-26 15:42:01
【问题描述】:

有没有办法让存根方法在后续调用中返回不同的对象?我想这样做来测试来自ExecutorCompletionService 的不确定响应。即测试无论方法的返回顺序如何,结果都保持不变。

我要测试的代码看起来像这样。

// Create an completion service so we can group these tasks together
ExecutorCompletionService<T> completionService =
        new ExecutorCompletionService<T>(service);

// Add all these tasks to the completion service
for (Callable<T> t : ts)
    completionService.submit(request);

// As an when each call finished, add it to the response set.
for (int i = 0; i < calls.size(); i ++) {
    try {
        T t = completionService.take().get();
        // do some stuff that I want to test
    } catch (...) { }        
}

【问题讨论】:

    标签: java mocking mockito


    【解决方案1】:

    这可能是基本的/显而易见的,但如果像我一样,你试图模拟一个方法的多次调用,每次调用要测试的方法被调用的次数未知,例如:

    public String method(String testArg) {
        //...
        while(condition) {
            someValue = someBean.nestedMethod(); // This is called unknown number of times
            //...
        }
        //...
    }
    

    你可以这样做:

    @Test
    public void testMethod() {
        mockNestedMethodForValue("value1");
        assertEquals(method("arg"), "expected1");
        mockNestedMethodForValue("value2");
        assertEquals(method("arg"), "expected2");
        mockNestedMethodForValue("value3");
        assertEquals(method("arg"), "expected3");
    }
    
    private void mockNestedMethodForValue(String value) {
        doReturn(value).when(someBeanMock).nestedMethod();
    }
    

    【讨论】:

      【解决方案2】:

      如果你有一个动态的值列表,你可以使用AdditionalAnswers.returnsElementsOf

      import org.mockito.AdditionalAnswers;
      
      when(mock.method()).thenAnswer(AdditionalAnswers.returnsElementsOf(myListOfValues));
      

      【讨论】:

        【解决方案3】:

        几乎所有的调用都是可链接的:

        doReturn(null).doReturn(anotherInstance).when(mock).method();
        

        【讨论】:

          【解决方案4】:

          这与问题没有直接关系。但想把它放在同一个链中。

          如果尝试使用多个参数验证相同的方法调用,您可以使用 Mockito 的以下时间功能。如果您不进行验证,则不需要它。

          Mockito.verify(method, times(n)).methoscall();

          这里的“n”是调用模拟的次数。

          【讨论】:

            【解决方案5】:

            BDD 风格:

            import static org.mockito.BDDMockito.given;
                    ...
            
                    given(yourMock.yourMethod()).willReturn(1, 2, 3);
            

            经典款式:

            import static org.mockito.Mockito.when;
                    ...
            
                    when(yourMock.yourMethod()).thenReturn(1, 2, 3);
            

            显式风格:

                    ...
            
                    when(yourMock.yourMethod())
                        .thenReturn(1)
                        .thenReturn(2)
                        .thenReturn(3);
            

            【讨论】:

              【解决方案6】:

              您可以使用LinkedListAnswer。例如

              MyService mock = mock(MyService.class);
              LinkedList<String> results = new LinkedList<>(List.of("A", "B", "C"));
              when(mock.doSomething(any())).thenAnswer(invocation -> results.removeFirst());
              

              【讨论】:

                【解决方案7】:

                这是 BDD 风格的工作示例,非常简单明了

                given(carRepository.findByName(any(String.class))).willReturn(Optional.empty()).willReturn(Optional.of(MockData.createCarEntity()));
                

                【讨论】:

                  【解决方案8】:

                  doReturn( value1, value2, value3 ).when( 方法调用 )

                  【讨论】:

                  • 你的意思是doReturn(value1, value2, value3).when(mock).methodCall() 可能吗?
                  【解决方案9】:

                  与 8 年前 @[Igor Nikolaev] 的回答相关,使用 Answer 可以使用 Java 8 中的 lambda expression 进行一些简化。

                  when(someMock.someMethod()).thenAnswer(invocation -> {
                      doStuff();
                      return;
                  });
                  

                  或更简单地说:

                  when(someMock.someMethod()).thenAnswer(invocation -> doStuff());
                  

                  【讨论】:

                    【解决方案10】:

                    您可以使用thenAnswer 方法来做到这一点(与when 链接时):

                    when(someMock.someMethod()).thenAnswer(new Answer() {
                        private int count = 0;
                    
                        public Object answer(InvocationOnMock invocation) {
                            if (count++ == 1)
                                return 1;
                    
                            return 2;
                        }
                    });
                    

                    或使用等效的静态doAnswer 方法:

                    doAnswer(new Answer() {
                        private int count = 0;
                    
                        public Object answer(InvocationOnMock invocation) {
                            if (count++ == 1)
                                return 1;
                    
                            return 2;
                        }
                    }).when(someMock).someMethod();
                    

                    【讨论】:

                    • 这个答案对我帮助很大,因为doAnswer()/thenAnswer() 不允许像doReturn()/thenReturn() 那样链接多个调用,我需要计算一些东西而不仅仅是返回一个不同的值.使用私有 count 变量创建匿名 Answer 对象对我来说是诀窍。
                    • 请记住,当someMethod() 返回void 时,这些是不等价的。有关详细信息,请参阅this 答案。
                    【解决方案11】:

                    作为previously pointed out,几乎所有的调用都是可链接的。

                    所以你可以打电话

                    when(mock.method()).thenReturn(foo).thenReturn(bar).thenThrow(new Exception("test"));
                    
                    //OR if you're mocking a void method and/or using spy instead of mock
                    
                    doReturn(foo).doReturn(bar).doThrow(new Exception("Test").when(mock).method();
                    

                    Mockito's Documenation 中的更多信息。

                    【讨论】:

                    • 非常有帮助!在这个例子中第四次调用mock.method 会发生什么?我想要类似的东西,第一次返回 foo 但其余的都返回 bar。
                    • 对 mock 的每次额外调用都会返回最后一个 'thenReturn' 或最后一个 'thenThrow' 非常有用
                    • 感谢您提供的伟大而简单的指示。直到现在才知道这一点。我一直在努力寻找如何在两个相同的电话中取回两个不同的结果。为我节省大量时间。
                    • 优秀的解决方案!使用这个。
                    【解决方案12】:

                    我已经实现了一个MultipleAnswer 类,它可以帮助我在每次通话中存根不同的答案。这是一段代码:

                    private final class MultipleAnswer<T> implements Answer<T> {
                    
                        private final ArrayList<Answer<T>> mAnswers;
                    
                        MultipleAnswer(Answer<T>... answer) {
                            mAnswers = new ArrayList<>();
                            mAnswers.addAll(Arrays.asList(answer));
                        }
                    
                        @Override
                        public T answer(InvocationOnMock invocation) throws Throwable {
                            return mAnswers.remove(0).answer(invocation);
                        }
                    }
                    

                    【讨论】:

                    • 你能用一种简短易读的方式初始化那个对象吗?
                    【解决方案13】:

                    以下可以作为常用方法在不同的方法调用上返回不同的参数。我们唯一需要做的就是传递一个数组,其中包含在每次调用中检索对象的顺序。

                    @SafeVarargs
                    public static <Mock> Answer<Mock> getAnswerForSubsequentCalls(final Mock... mockArr) {
                        return new Answer<Mock>() {
                           private int count=0, size=mockArr.length;
                           public Mock answer(InvocationOnMock invocation) throws throwable {
                               Mock mock = null;
                               for(; count<size && mock==null; count++){
                                    mock = mockArr[count];
                               }
                    
                               return mock;    
                           } 
                        }
                    }
                    

                    例如。 getAnswerForSubsequentCalls(mock1, mock3, mock2); 将在第一次调用时返回 mock1 对象,在第二次调用时返回 mock3 对象,在第三次调用时返回 mock2 对象。 应该像when(something()).doAnswer(getAnswerForSubsequentCalls(mock1, mock3, mock2));一样使用 这几乎类似于when(something()).thenReturn(mock1, mock3, mock2);

                    【讨论】:

                      【解决方案14】:

                      怎么样

                      when( method-call ).thenReturn( value1, value2, value3 );
                      

                      您可以在 thenReturn 的括号中放置任意数量的参数,只要它们都是正确的类型。第一次调用该方法时将返回第一个值,然后是第二个答案,依此类推。一旦所有其他值都用完,最后一个值将重复返回。

                      【讨论】:

                      • 这适用于模拟,但不适用于间谍。如果需要阻止调用原始方法,则需要 doAnswer(...).when(someSpy).someMethod(...)。
                      • @Yuri - 不完全是。在您提到的情况下,您不需要doAnswer 或写Answer。你可以使用doReturn(...).when(someSpy).someMethod(...)。假设艾玛对模拟而不是间谍感兴趣似乎是合理的,但我想我可以在我的答案中添加一些内容来说明这一点。感谢您的评论。
                      • @DawoodibnKareem 假设第一次调用我想返回一个值,第二次调用我想抛出一个异常。如何做到这一点?
                      • @Rito 请阅读 Volodymyr 的回答或 Raystorm 的回答。他们都涵盖了那个案例。
                      • 这样一个光荣的答案。
                      猜你喜欢
                      • 2015-10-07
                      • 1970-01-01
                      • 1970-01-01
                      • 2020-11-28
                      • 1970-01-01
                      • 2016-06-16
                      • 2012-01-17
                      • 2012-11-08
                      相关资源
                      最近更新 更多