如何使用Mockito模拟void方法


937

如何用void返回类型模拟方法?

我实现了一个观察者模式,但是我无法用Mockito对其进行模拟,因为我不知道如何做。

我试图在Internet上找到一个例子,但没有成功。

我的课看起来像这样:

public class World {

    List<Listener> listeners;

    void addListener(Listener item) {
        listeners.add(item);
    }

    void doAction(Action goal,Object obj) {
        setState("i received");
        goal.doAction(obj);
        setState("i finished");
    }

    private string state;
    //setter getter state
} 

public class WorldTest implements Listener {

    @Test public void word{
    World  w= mock(World.class);
    w.addListener(this);
    ...
    ...

    }
}

interface Listener {
    void doAction();
}

系统不会通过模拟触发。

我想显示上述系统状态。并根据他们做出断言。


6
注意,默认情况下,模拟上的void方法什么也不做!

1
@Line,这就是我想要的。说完之后似乎很明显。但这确实凸显了一种模拟原理:您只需要模拟模拟类的方法的效果,例如返回值或异常。谢谢!
allenjom

Answers:


1144

看一下Mockito API文档。由于链接的文档提到(点#12),你可以使用任何的doThrow()doAnswer()doNothing()doReturn()家人从框架的Mockito的方法来嘲笑无效的方法。

例如,

Mockito.doThrow(new Exception()).when(instance).methodName();

或者如果您想将其与后续行为结合起来,

Mockito.doThrow(new Exception()).doNothing().when(instance).methodName();

假设您要在setState(String s)下面的World类doAnswer中模拟设置器,那么代码将使用方法模拟setState

World  mockWorld = mock(World.class); 
doAnswer(new Answer<Void>() {
    public Void answer(InvocationOnMock invocation) {
      Object[] args = invocation.getArguments();
      System.out.println("called with arguments: " + Arrays.toString(args));
      return null;
    }
}).when(mockWorld).setState(anyString());

8
@qualidafial:是的,我认为对Void进行参数化会更好,因为它可以更好地表达我对返回类型不感兴趣。我不知道这种构造,感谢您指出。
sateesh 2010年

2
doThrow现在是#5(也对我来说,使用doThrow可以解决以下问题:“对于此处的追随者来说,此处不允许'void'类型” ...)
rogerdpack 2012年

@qualidafial:我认为Answer.answer调用的返回类型不是返回原始方法的类型,而是doAnswer调用返回的类型,大概是您想在测试中使用该值执行其他操作。
2014年

1
:(试图在guava doNothing()中模拟RateLimiter.java的16.0.1版本因此,它没有嘲笑我的setRate方法:(而是称它为:(
Dean Hiller

2
@DeanHiller注意setRate()final,因此不能被嘲笑。而是尝试- create()做一个满足您需要的实例。不必嘲笑RateLimiter
dimo414 '16

113

我想我已经找到了这个问题的简单答案,只需将一个真正的方法调用为一个方法(即使它具有空返回值),也可以执行以下操作:

Mockito.doCallRealMethod().when(<objectInstance>).<method>();
<objectInstance>.<method>();

或者,您可以为该类的所有方法调用real方法,方法是:

<Object> <objectInstance> = mock(<Object>.class, Mockito.CALLS_REAL_METHODS);

13
这是真正的答案。spy()方法可以正常工作,但通常在您希望对象正常执行大多数操作时保留。
biggusjimmus 2014年

1
这是什么意思?您实际上是在调用方法吗?我以前从未真正使用过模仿。
obesechicken13年

是的,该模拟将调用真实方法。如果使用@Mock,则可以指定以下内容:@Mock(answer = Answers.CALLS_REAL_METHODS)以获取相同的结果。
Ale

70

加上@sateesh所说的,当您只想模拟一个void方法以防止测试调用它时,可以使用Spy以下方式:

World world = new World();
World spy = Mockito.spy(world);
Mockito.doNothing().when(spy).methodToMock();

当您要运行测试时,请确保在spy对象上而不是对象上调用测试中的方法world。例如:

assertEquals(0,spy.methodToTestThatShouldReturnZero());

57

所谓问题的解决方案是使用spy Mockito.spy(...)而不是mock Mockito.mock(..)

间谍使我们能够进行部分嘲笑。Mockito在这件事上很擅长。由于您的班级不完整,因此您可以在该班级中模拟一些必需的位置。


3
我在这里偶然发现是因为我有一个类似的问题(同时,碰巧是正在测试主题/观察者交互)。我已经在使用间谍,但是我想让'SubjectChanged'方法做一些不同的事情。我可以使用`verify(observer).subjectChanged(subject)来查看方法是否被调用。但是,由于某种原因,我宁愿重写该方法。为此,将Sateesh的方法与您的答案结合起来是必经之路……
gMale

32
不,这样做实际上对模拟void方法没有帮助。诀窍是使用sateesh答案中列出的四种Mockito静态方法之一。
2013年

2
@Gurnard提出的问题请看一下stackoverflow.com/questions/1087339/…
ibrahimyilmaz

这个剂量实际上起作用。
Nilotpal

34

首先:您应该始终导入Mockito static,这样,代码将更具可读性(直观):

import static org.mockito.Mockito.*;

对于部分模拟并在其余部分上仍保留原始功能,mockito提供了“间谍”功能。

您可以按以下方式使用它:

private World world = spy(World.class);

要消除执行方法,可以使用以下方法:

doNothing().when(someObject).someMethod(anyObject());

为方法提供一些自定义行为,请使用“ when”和“ thenReturn”:

doReturn("something").when(this.world).someMethod(anyObject());

有关更多示例,请在文档中找到出色的Mockito示例。


4
静态导入与提高可读性有什么关系?
jsonbourne

2
没什么。
specializt

2
我认为这是一个品味问题,我只是想使语句看起来(几乎)像一个英语句子,而Class.methodname()。something()与methodname()的关系。
fl0w

1
如果您在团队中工作,则import static会使其他人很难理解该方法的来源,因为导入过程中通常会使用通配符。
LowKeyEnergy

是正确的,但是对于测试代码和Mockito,这应该是显而易见的,而不是问题。
fl0w

26

如何使用Mockito模拟void方法-有两种选择:

  1. doAnswer -如果我们想要模拟的void方法做某事(尽管无效,也要模拟行为)。
  2. doThrow-然后,Mockito.doThrow()如果要从模拟的void方法中引发异常,就会出现这种情况。

以下是如何使用它的示例(不是理想的用例,只是想说明基本用法)。

@Test
public void testUpdate() {

    doAnswer(new Answer<Void>() {

        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            Object[] arguments = invocation.getArguments();
            if (arguments != null && arguments.length > 1 && arguments[0] != null && arguments[1] != null) {

                Customer customer = (Customer) arguments[0];
                String email = (String) arguments[1];
                customer.setEmail(email);

            }
            return null;
        }
    }).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("old@test.com", "new@test.com");

    //some asserts
    assertThat(customer, is(notNullValue()));
    assertThat(customer.getEmail(), is(equalTo("new@test.com")));

}

@Test(expected = RuntimeException.class)
public void testUpdate_throwsException() {

    doThrow(RuntimeException.class).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("old@test.com", "new@test.com");

}
}

您可以在我的文章如何使用Mockito进行模拟(有关示例的综合指南)中找到有关如何使用Mockito 进行模拟测试 void方法的更多详细信息。


1
很好的例子。注意:在Java 8中,最好使用lambda代替匿名类:'doAnswer((Answer <Void>)invocation-> {// CODE})。when(mockInstance).add(method ());'
miwe

16

向该串添加另一个答案(无双关语)...

如果您不希望使用间谍软件,则必须调用doAnswer方法。但是,您不必滚动自己的Answer。有几种默认实现。值得注意的是,CallsRealMethods

实际上,它看起来像这样:

doAnswer(new CallsRealMethods()).when(mock)
        .voidMethod(any(SomeParamClass.class));

要么:

doAnswer(Answers.CALLS_REAL_METHODS.get()).when(mock)
        .voidMethod(any(SomeParamClass.class));

16

在Java 8中,假设您具有以下内容的静态导入,则可以使其更加简洁org.mockito.Mockito.doAnswer

doAnswer(i -> {
  // Do stuff with i.getArguments() here
  return null;
}).when(*mock*).*method*(*methodArguments*);

return null;是很重要的,没有它编译将一些比较隐蔽的错误失败,因为它无法找到一个合适的替代doAnswer

例如ExecutorService,可以立即执行任何Runnable传递给的,execute()可以使用以下方法实现:

doAnswer(i -> {
  ((Runnable) i.getArguments()[0]).run();
  return null;
}).when(executor).execute(any());

2
在一行中:Mockito.doAnswer((i)-> null).when(instance).method(any());
阿克瑟·索夫

@AkshayThorve当您实际上想和我一起做东西时,这不起作用。
蒂姆B,

7

我认为您的问题归结于您的测试结构。我发现很难将模拟与在测试类中实现接口的传统方法混合使用(如您在此处所做的那样)。

如果将侦听器实现为模拟,则可以验证交互。

Listener listener = mock(Listener.class);
w.addListener(listener);
world.doAction(..);
verify(listener).doAction();

这应该使您确信“世界”正在做正确的事情。


0

使用Mockito.doThrow的方式如下:

Mockito.doThrow(new Exception()).when(instance).methodName();

您可以尝试以下示例:

public void testCloseStreamOnException() throws Exception {
    OutputStream outputStream = Mockito.mock(OutputStream.class);
    IFileOutputStream ifos = new IFileOutputStream(outputStream);
    Mockito.doThrow(new IOException("Dummy Exception")).when(outputStream).flush();
    try {
      ifos.close();
      fail("IOException is not thrown");
    } catch (IOException ioe) {
      assertEquals("Dummy Exception", ioe.getMessage());
    }
    Mockito.verify(outputStream).close();
  }

来源:http//apisonar.com/java-examples/org.mockito.Mockito.doThrow.html#Example-19

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.