Mockito: doAnswer与thenReturn的区别

191

我正在使用Mockito进行服务层单元测试。我不清楚何时使用doAnswerthenReturn

有人可以详细帮助我吗?到目前为止,我已经尝试了使用thenReturn

3个回答

237
当您在模拟方法调用时,如果您知道返回值,应使用thenReturndoReturn。当您调用模拟方法时,将返回定义的值。

thenReturn(T value)设置在调用方法时返回的值。

@Test
public void test_return() throws Exception {
    Dummy dummy = mock(Dummy.class);
    int returnValue = 5;

    // choose your preferred way
    when(dummy.stringLength("dummy")).thenReturn(returnValue);
    doReturn(returnValue).when(dummy).stringLength("dummy");
}

Answer用于在调用模拟方法时需要执行其他操作的情况,例如在您需要根据此方法调用的参数计算返回值时。

当您想要使用通用Answer存根void方法时,请使用doAnswer()

Answer指定了在与模拟对象交互时执行的操作和返回的返回值。

@Test
public void test_answer() throws Exception {
    Dummy dummy = mock(Dummy.class);
    Answer<Integer> answer = new Answer<Integer>() {
        public Integer answer(InvocationOnMock invocation) throws Throwable {
            String string = invocation.getArgumentAt(0, String.class);
            return string.length() * 2;
        }
    };

    // choose your preferred way
    when(dummy.stringLength("dummy")).thenAnswer(answer);
    doAnswer(answer).when(dummy).stringLength("dummy");
}

嗨@Roland Weisleder,但有时您应该返回一些由内部代码生成的值,与参数无关,例如code = UUID.randomUUID(),我发现使用mockito实现这一点是不可能的。 - zhuguowei
4
当您的模拟应在每次调用时返回一个新的UUID时,您只需使用return UUID.randomUUID();实现Answer - Roland Weisleder
我能否将新答案初始化的方法提取出来放到某个方法中,以使代码更加简洁? - Line
3
@Line Answer 是一个函数式接口,所以使用Java 8,你可以用一个lambda表达式来替换它。如果代码不够清晰,也可以进行其他常见和不常见的重构。 - Roland Weisleder
@ zhuguowei:返回一些内部代码生成的值?你是什么意思? - Saurabh Patil

60

doAnswerthenReturn 在以下情况下才会执行相同的操作:

  1. 您使用的是 Mock 而不是 Spy。
  2. 您正在 stubbing 的方法正在返回一个值,而不是一个 void 方法。

让我们模拟这个 BookService。

public interface BookService {
    String getAuthor();
    void queryBookTitle(BookServiceCallback callback);
}

您可以使用doAnswerthenReturn来桩造getAuthor()

BookService service = mock(BookService.class);
when(service.getAuthor()).thenReturn("Joshua");
// or..
doAnswer(new Answer() {
    @Override
    public Object answer(InvocationOnMock invocation) throws Throwable {
        return "Joshua";
    }
}).when(service).getAuthor();
请注意,在使用doAnswer时,您无法将when方法传递给它。
// Will throw UnfinishedStubbingException
doAnswer(invocation -> "Joshua").when(service.getAuthor());

那么,什么情况下您会使用doAnswer而不是thenReturn呢? 我可以想到两种用例:

  1. 当您想要“stub” void方法时。

使用doAnswer,您可以在方法调用时执行一些额外的操作。例如,在查询书名时触发回调。

BookServiceCallback callback = new BookServiceCallback() {
    @Override
    public void onSuccess(String bookTitle) {
        assertEquals("Effective Java", bookTitle);
    }
};
doAnswer(new Answer() {
    @Override
    public Object answer(InvocationOnMock invocation) throws Throwable {
        BookServiceCallback callback = (BookServiceCallback) invocation.getArguments()[0];
        callback.onSuccess("Effective Java");
        // return null because queryBookTitle is void
        return null;
    }
}).when(service).queryBookTitle(callback);
service.queryBookTitle(callback);
  1. 当你使用Spy而不是Mock时

在Spy Mockito中使用when-thenReturn将调用真实方法,然后存根您的答案。如果您不想调用真实方法,就像在此示例中一样,这可能会导致问题:

List list = new LinkedList();
List spy = spy(list);
// Will throw java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
when(spy.get(0)).thenReturn("java");
assertEquals("java", spy.get(0));

使用 doAnswer 我们可以安全地进行存根。

List list = new LinkedList();
List spy = spy(list);
doAnswer(invocation -> "java").when(spy).get(0);
assertEquals("java", spy.get(0));

实际上,如果您不想在方法调用时执行其他操作,您可以直接使用doReturn

List list = new LinkedList();
List spy = spy(list);
doReturn("java").when(spy).get(0);
assertEquals("java", spy.get(0));

如果被模拟的方法是void类型怎么办? - Igor Donin
1
Igor,这正是doAnswer()发挥作用的地方,他在上面的答案中已经涵盖了这一点。 - Saurabh Patil
当使用doAnswer(new Answer() { ... return null;}时,我在Eclipse中会收到一个警告:“Answer is a raw type. References to generic type Answer<T> should be parameterized”。除了忽略警告之外,有没有解决这个问题的方法? - LazR
1
请注意,在使用 doAnswer 时,你不能传递一个方法。这一行代码帮了我大忙,谢谢! - mnagdev

14

最简单的答案是:

  • 如果你需要在方法调用时返回一个固定的值,那么我们应该使用thenReturn(…)
  • 如果你需要执行一些操作或者值需要在运行时计算,那么我们应该使用thenAnswer(…)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接