ChatGPT解决这个技术问题 Extra ChatGPT

如何使用 Mockito 模拟 void 方法

如何模拟具有 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();
}

系统不会被模拟触发。

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

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

A
Ahmed Ashour

看看 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 类中模拟 setter 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 字节编码,mutex 出于某种原因为 null所以它没有模拟我的 setRate 方法 :( 而是调用它 :(
@DeanHiller 注意到 setRate()final,因此不能被嘲笑。而是尝试 create()-ing 一个可以满足您需要的实例。不需要模拟 RateLimiter
M
MarcioB

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

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

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

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

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

添加到@sateesh 所说的内容,当您只想模拟一个 void 方法以防止测试调用它时,您可以通过这种方式使用 Spy

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

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

assertEquals(0, spy.methodToTestThatShouldReturnZero());

P
Peter Coulton

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

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


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

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

import static org.mockito.Mockito.*;

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

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

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

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

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

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

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

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


D
Dilini Rajapaksha

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

doAnswer - 如果我们希望我们模拟的 void 方法做某事(尽管是 void,但模拟行为)。 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");

}
}

您可以在我的帖子中找到更多关于如何使用 Mockito mocktest void 方法的详细信息How to mock with Mockito (A comprehensive guide with examples)


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

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

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

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

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

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

在一行中: Mockito.doAnswer((i) -> null).when(instance).method(any());
@AkshayThorve 但是,当您实际上想对 i 做一些事情时,那是行不通的。
p
poussma

为这群人添加另一个答案(没有双关语)......

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

在实践中,它看起来像这样:

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

或者:

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

a
ashley

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

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

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

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


K
Kıvılcım

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

假设您有一个自动连接 GalaxyService 的 SpaceService,它有一个名为 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 方法需要一个 planet 参数。在方法中做一些事情。看 :

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

N
Nikita

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