如何模拟具有 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();
}
系统不会被模拟触发。
我想显示上述系统状态。并据此作出断言。
看看 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());
我想我已经为这个问题找到了一个更简单的答案,只为一种方法调用真正的方法(即使它有一个 void 返回)你可以这样做:
Mockito.doCallRealMethod().when(<objectInstance>).<method>();
<objectInstance>.<method>();
或者,您可以为该类的所有方法调用真正的方法,这样做:
<Object> <objectInstance> = mock(<Object>.class, Mockito.CALLS_REAL_METHODS);
添加到@sateesh 所说的内容,当您只想模拟一个 void 方法以防止测试调用它时,您可以通过这种方式使用 Spy
:
World world = new World();
World spy = Mockito.spy(world);
Mockito.doNothing().when(spy).methodToMock();
当您要运行测试时,请确保在 spy
对象而不是 world
对象上调用测试中的方法。例如:
assertEquals(0, spy.methodToTestThatShouldReturnZero());
所谓问题的解决方法是使用 spy
Mockito.spy(...) 而不是 mock
Mockito.mock(..)。
Spy 使我们能够进行部分模拟。 Mockito 擅长这个问题。因为你有一个不完整的课程,所以你在这个课程中模拟了一些需要的地方。
首先:您应该始终导入 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 示例。
如何使用 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 mock 和 test void 方法的详细信息How to mock with Mockito (A comprehensive guide with examples)
在 Java 8 中,这可以变得更简洁一些,假设您有 org.mockito.Mockito.doAnswer
的静态导入:
doAnswer(i -> {
// Do stuff with i.getArguments() here
return null;
}).when(*mock*).*method*(*methodArguments*);
return null;
很重要,如果没有它,编译将失败并出现一些相当模糊的错误,因为它无法为 doAnswer
找到合适的覆盖。
例如,立即执行传递给 execute()
的任何 Runnable
的 ExecutorService
可以使用以下方法实现:
doAnswer(i -> {
((Runnable) i.getArguments()[0]).run();
return null;
}).when(executor).execute(any());
为这群人添加另一个答案(没有双关语)......
如果您不能\不想使用间谍,您确实需要调用 doAnswer 方法。但是,您不一定需要推出自己的 Answer。有几个默认实现。值得注意的是,CallsRealMethods。
在实践中,它看起来像这样:
doAnswer(new CallsRealMethods()).when(mock)
.voidMethod(any(SomeParamClass.class));
或者:
doAnswer(Answers.CALLS_REAL_METHODS.get()).when(mock)
.voidMethod(any(SomeParamClass.class));
我认为你的问题是由于你的测试结构。我发现很难将模拟与在测试类中实现接口的传统方法混合在一起(正如您在此处所做的那样)。
如果您将侦听器实现为 Mock,则可以验证交互。
Listener listener = mock(Listener.class);
w.addListener(listener);
world.doAction(..);
verify(listener).doAction();
这应该让您满意,“世界”正在做正确的事情。
如果您需要在模拟的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);
在您的示例中,您应该模拟 Listener 项目并使用 Mockito.verify 来检查与它的交互
setRate()
是final
,因此不能被嘲笑。而是尝试create()
-ing 一个可以满足您需要的实例。不需要模拟RateLimiter
。