简体   繁体   English

"如何使用 Mockito 模拟 void 方法"

[英]How to mock void methods with Mockito

How to mock methods with void return type?如何模拟具有 void 返回类型的方法?

I implemented an observer pattern but I can't mock it with Mockito because I don't know how.我实现了一个观察者模式,但我不能用 Mockito 模拟它,因为我不知道怎么做。

And I tried to find an example on the Internet but didn't succeed.我试图在互联网上找到一个例子但没有成功。

My class looks like this:我的课看起来像这样:

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

The system is not triggered with mock.系统不会被模拟触发。

I want to show the above-mentioned system state.我想显示上述系统状态。 And make assertions according to them.并据此作出断言。

Take a look at the Mockito API docs .查看 Mockito API 文档 As the linked document mentions (Point # 12) you can use any of the doThrow() , doAnswer() , doNothing() , doReturn() family of methods from Mockito framework to mock void methods.正如链接文档中提到的(第 12 点),您可以使用doThrow()任何doThrow()doAnswer()doNothing()doReturn()系列方法来模拟 void 方法。

For example,例如,

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

or if you want to combine it with follow-up behavior,或者如果你想把它与后续行为结合起来,

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

Presuming that you are looking at mocking the setter setState(String s) in the class World below is the code uses doAnswer method to mock the setState .假设您正在考虑在下面的类 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());

I think I've found a simpler answer to that question, to call the real method for just one method (even if it has a void return) you can do this:我想我已经找到了一个更简单的答案,只为一个方法调用真正的方法(即使它有一个 void 返回),你可以这样做:

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

Or, you could call the real method for all methods of that class, doing this:或者,您可以为该类的所有方法调用真正的方法,这样做:

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

Adding to what @sateesh said, when you just want to mock a void method in order to prevent the test from calling it, you could use a Spy this way:补充一下@sateesh 所说的,当您只想模拟一个 void 方法以防止测试调用它时,您可以这样使用Spy

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

When you want to run your test, make sure you call the method in test on the spy object and not on the world object.当你想运行你的测试时,确保你在spy对象上而不是在world对象上调用 test 中的方法。 For example:例如:

assertEquals(0, spy.methodToTestThatShouldReturnZero());

The solution of so-called problem is to use a spy Mockito.spy(...) instead of a mock Mockito.mock(..) .所谓问题的解决方案是使用spy Mockito.spy(...)而不是mock Mockito.mock(..)

Spy enables us to partial mocking. Spy 使我们能够进行部分模拟。 Mockito is good at this matter. Mockito 擅长这件事。 Because you have class which is not complete, in this way you mock some required place in this class.因为您的课程不完整,因此您可以通过这种方式模拟该课程中的某些必需位置。

First of all: you should always import mockito static, this way the code will be much more readable (and intuitive):首先:您应该始终导入 mockito static,这样代码将更具可读性(和直观):

import static org.mockito.Mockito.*;

For partial mocking and still keeping original functionality on the rest mockito offers "Spy".对于部分模拟并仍然保留其余部分的原始功能,mockito 提供“间谍”。

You can use it as follows:您可以按如下方式使用它:

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

To eliminate a method from being executed you could use something like this:要消除正在执行的方法,您可以使用以下内容:

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

to give some custom behaviour to a method use "when" with an "thenReturn":为方法使用“when”和“thenReturn”提供一些自定义行为:

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

For more examples please find the excellent mockito samples in the doc.有关更多示例,请在文档中找到优秀的 mockito 示例。

How to mock void methods with mockito - there are two options:如何使用 mockito 模拟 void 方法 - 有两种选择:

  1. doAnswer - If we want our mocked void method to do something (mock the behavior despite being void). doAnswer - 如果我们希望我们的模拟 void 方法做某事(尽管无效,但模拟行为)。
  2. doThrow - Then there is Mockito.doThrow() if you want to throw an exception from the mocked void method. doThrow - 如果您想从Mockito.doThrow()的 void 方法中抛出异常,则可以使用Mockito.doThrow()

Following is an example of how to use it (not an ideal usecase but just wanted to illustrate the basic usage).以下是如何使用它的示例(不是理想的用例,只是想说明基本用法)。

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

}
}

You could find more details on how to mock and test void methods with Mockito in my post How to mock with Mockito (A comprehensive guide with examples)您可以在我的文章How to mock with Mockito(带示例的综合指南)中找到有关如何使用 Mockito模拟测试void方法的更多详细信息

In Java 8 this can be made a little cleaner, assuming you have a static import for org.mockito.Mockito.doAnswer :在 Java 8 中,这可以更org.mockito.Mockito.doAnswer一些,假设你有一个org.mockito.Mockito.doAnswer的静态导入:

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

The return null; return null; is important and without it the compile will fail with some fairly obscure errors as it won't be able to find a suitable override for doAnswer .很重要,如果没有它,编译将失败并出现一些相当模糊的错误,因为它将无法为doAnswer找到合适的覆盖。

For example an ExecutorService that just immediately executes any Runnable passed to execute() could be implemented using:例如,一个ExecutorService立即执行传递给execute()任何Runnable可以使用以下方法实现:

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

Adding another answer to the bunch (no pun intended)...添加另一个答案到一堆(没有双关语意)...

You do need to call the doAnswer method if you can't\\don't want to use spy's.如果您不能\\不想使用间谍,则确实需要调用 doAnswer 方法。 However, you don't necessarily need to roll your own Answer .但是,您不一定需要推出自己的Answer There are several default implementations.有几个默认实现。 Notably, CallsRealMethods .值得注意的是, CallsRealMethods

In practice, it looks something like this:在实践中,它看起来像这样:

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

Or:或者:

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

I think your problems are due to your test structure.我认为您的问题是由于您的测试结构造成的。 I've found it difficult to mix mocking with the traditional method of implementing interfaces in the test class (as you've done here).我发现很难将模拟与在测试类中实现接口的传统方法混合使用(正如您在此处所做的那样)。

If you implement the listener as a Mock you can then verify the interaction.如果将侦听器实现为 Mock,则可以验证交互。

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

This should satisfy you that the 'World' is doing the right thing.这应该让您满意,“世界”正在做正确的事情。

If you need to do some operations in the mocked void method, and you need to manipulate the argument that sent to void method;如果您需要在模拟的void方法中进行一些操作,并且您需要操作发送给void方法的参数; you can combine Mockito.doAnswer with ArgumentCaptor.capture method.您可以将 Mockito.doAnswer 与 ArgumentCaptor.capture 方法结合使用。

Let's say you have SpaceService that autowires a GalaxyService , which has a void method called someServiceMethod .假设您有一个自动装配GalaxyServiceSpaceService ,它有一个名为someServiceMethod的 void 方法。

You want to write test for one of your method in SpaceService that calls GalaxyService 's void method.您想为SpaceService中调用GalaxyService的 void 方法的方法之一编写测试。 Your planet is also generated inside SpaceService.你的星球也在 SpaceService 中生成。 So you don't have any chance to mock that.所以你没有任何机会嘲笑它。

Here is your sample SpaceService class that you want to write tests for.这是您要为其编写测试的示例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();
    }
}

The GalaxyService.someServiceMethod method expects a planet argument. GalaxyService.someServiceMethod方法需要一个 planet 参数。 Does some stuff in the method.在方法中做一些事情。 See :看 :

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.
    }
}

And you want to test this feature.并且您想测试此功能。

Here is an example :这是一个例子:

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

It can be done in 2 ways depending upon the scenarios as : 根据不同的场景,可以通过2种方式来完成:

  1. doAnswer - If we want our mocked void method to do something (mock the behavior despite being void). doAnswer-如果我们希望模拟的void方法做某事(尽管无效,也要模拟行为)。

  2. doThrow - We also have Mockito.doThrow() if you want to throw an exception from the mocked void method. doThrow-如果您想从模拟的void方法抛出异常,我们也有Mockito.doThrow()。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM