【问题标题】:How to mock void methods with Mockito如何使用 Mockito 模拟 void 方法
【发布时间】:2011-01-17 14:09:50
【问题描述】:

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

我实现了一个观察者模式,但我不能用 Mockito 模拟它,因为我不知道怎么做。

而且我试图在网上找到一个例子但没有成功。

我的班级是这样的:

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();
}

系统不是用mock触发的。

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

【问题讨论】:

  • 注意模拟上的 void 方法默认什么都不做!
  • @Line,这就是我要找的。说出来之后就很明显了。但它确实突出了一个模拟原则:您只需要模拟模拟类的方法以获得它们的效果,例如返回值或异常。谢谢!

标签: java unit-testing mocking mockito void


【解决方案1】:

如果你需要在mocked void方法中做一些操作,并且你需要对发送给void方法的参数进行操作;您可以将 Mockito.doAnswer 与 ArgumentCaptor.capture 方法结合使用。

假设您有一个 SpaceService,它自动连接了一个 GalaxyService,它有一个名为 someServiceMethod 的 void 方法。

您想为 SpaceService 中调用 GalaxyService 的 void 方法的方法之一编写测试。你的星球也在 SpaceService 中生成。所以你没有任何机会嘲笑它。

这是您要为其编写测试的示例 SpaceService 类。

class SpaceService {
    @Autowired
    private GalaxyService galaxyService;

    public Date someCoolSpaceServiceMethod() {
        // does something

        Planet planet = new World();
        galaxyService.someServiceMethod(planet); //Planet updated in this method.

        return planet.getCurrentTime();
    }
}

GalaxyService.someServiceMethod 方法需要一个行星参数。在方法中做一些事情。见:

GalaxyService {
    public void someServiceMethod(Planet planet) {
        //do fancy stuff here. about solar system etc.

        planet.setTime(someCalculatedTime); // the thing that we want to test.

        // some more stuff.
    }
}

你想测试这个功能。

这是一个例子:

ArgumentCaptor<World> worldCaptor = ArgumentCaptor.forClass(World.class);
Date testDate = new Date();

Mockito.doAnswer(mocked-> {
    World capturedWorld = worldCaptor.getValue();
    world.updateTime(testDate);
    return null;
}).when(galaxyService.someServiceMethod(worldCaptor.capture());

Date result = spaceService.someCoolSpaceServiceMethod();

assertEquals(result, testDate);

【讨论】:

    【解决方案2】:

    在您的示例中,您应该模拟 Listener 项目并使用 Mockito.verify 来检查与它的交互

    【讨论】:

      【解决方案3】:

      除了@sateesh 所说的,当您只想模拟一个 void 方法以防止测试调用它时,您可以这样使用Spy

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

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

      assertEquals(0, spy.methodToTestThatShouldReturnZero());
      

      【讨论】:

        【解决方案4】:

        看看 Mockito API docs。正如链接文档中提到的(第 12 点),您可以使用 Mockito 框架中的任何doThrow()doAnswer()doNothing()doReturn() 系列方法来模拟 void 方法。

        例如,

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

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

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

        假设您正在查看在下面的 World 类中模拟设置器 setState(String s),代码使用 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());
        

        【讨论】:

        • @qualidafial:是的,我认为参数化为 Void 会更好,因为它更好地传达了我对返回类型不感兴趣。我不知道这个结构,谢谢指出。
        • doThrow 现在是#5(对于我来说,使用 doThrow 这修复了消息“此处不允许使用'void'类型”,对于追随者......)
        • @qualidafial:我认为 Answer.answer 调用的返回类型不是返回到原始方法的返回类型,而是返回到 doAnswer 调用的返回类型,大概是如果你想做其他事情的话测试中的那个值。
        • :( 试图在 guava doNothing().when(mockLimiterReject).setRate(100) 中模拟 RateLimiter.java 的 16.0.1 版本导致调用 RateLimiter 的 setRate 导致 nullpointer 因为互斥锁出于某种原因,一旦 mockito 对其进行字节编码,它就为 null,因此它没有模拟我的 setRate 方法:(而是调用它:(
        • @DeanHiller 注意到setRate()final,因此不能被模拟。而是尝试create()-ing 一个可以满足您需要的实例。应该没有必要模拟RateLimiter
        【解决方案5】:

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

        import static org.mockito.Mockito.*;
        

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

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

        private World world = spy(new World());
        

        要消除执行的方法,您可以使用以下内容:

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

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

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

        如需更多示例,请在文档中查找优秀的 mockito 示例。

        【讨论】:

          【解决方案6】:

          在 Java 8 中,这可以更简洁一些,假设您有一个 org.mockito.Mockito.doAnswer 的静态导入:

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

          return null; 很重要,如果没有它,编译将失败并出现一些相当模糊的错误,因为它无法为 doAnswer 找到合适的覆盖。

          例如,一个ExecutorService 可以立即执行传递给execute() 的任何Runnable,可以使用:

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

          【讨论】:

          • 一行:Mockito.doAnswer((i) -> null).when(instance).method(any());
          • @AkshayThorve 但是,当你真的想用 i 做一些事情时,这是行不通的。
          【解决方案7】:

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

          1. doAnswer - 如果我们希望我们模拟的 void 方法做某事(尽管是 void,但模拟行为)。
          2. doThrow - 如果你想从模拟的 void 方法中抛出异常,还有 Mockito.doThrow()

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

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

          您可以在我的帖子How to mock with Mockito (A comprehensive guide with examples)

          中找到有关如何使用 Mockito 模拟测试 void方法的更多详细信息

          【讨论】:

          • 很好的例子。注意:在 java 8 中,使用 lambda 代替匿名类可能会更好一些:'doAnswer((Answer) invocation -> { //CODE }).when(mockInstance).add(method ());'
          【解决方案8】:

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

          Spy 使我们能够进行部分模拟。 Mockito 擅长这个问题。因为你有一个不完整的课程,所以你在这个课程中模拟了一些需要的地方。

          【讨论】:

          • 我偶然发现了这里,因为我遇到了类似的问题(巧合的是,碰巧正在测试主题/观察者的交互)。我已经在使用间谍,但我希望“SubjectChanged”方法做一些不同的事情。我可以使用 `verify(observer).subjectChanged(subject) 来查看是否调用了该方法。但是,出于某种原因,我宁愿重写该方法。为此,将 Sateesh 的方法和您的答案结合起来就是要走的路……
          • 不,这样做实际上无助于模拟 void 方法。诀窍是使用 sateesh 的答案中列出的四种 Mockito 静态方法之一。
          • @Gurnard 你的问题看看这个stackoverflow.com/questions/1087339/…
          【解决方案9】:

          我认为您的问题是由于您的测试结构造成的。我发现很难将模拟与在测试类中实现接口的传统方法混合起来(就像您在此处所做的那样)。

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

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

          这应该让您满意,“世界”正在做正确的事情。

          【讨论】:

            【解决方案10】:

            我想我已经为这个问题找到了一个更简单的答案,只为一个方法调用真正的方法(即使它有一个 void 返回)你可以这样做:

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

            或者,您可以为该类的所有方法调用真正的方法,这样做:

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

            【讨论】:

            • 这是真正的答案。 spy() 方法可以正常工作,但通常是为您希望对象正常执行大部分操作而保留的。
            • 这是什么意思?你真的在调用这些方法吗?我以前没有真正使用过 mockito。
            • 是的,mock 会调用真正的方法。如果您使用@Mock,您可以指定相同的内容:@Mock(answer = Answers.CALLS_REAL_METHODS) 以获得相同的结果。
            • 如果是调用抽象方法来实现运行时多态,那么将.doCallRealMethod()替换为doNothing()
            【解决方案11】:

            添加另一个答案(没有双关语)...

            如果您不能\不想使用 spy,则确实需要调用 doAnswer 方法。但是,您不一定需要推出自己的Answer。有几个默认实现。值得注意的是,CallsRealMethods

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

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

            或者:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2021-02-04
              • 2014-09-21
              • 1970-01-01
              • 2020-12-08
              相关资源
              最近更新 更多