我有一个调用异步函数的方法:
public class MyService {
...
public void uploadData() {
MyPool.getInstance().getThreadPool().execute(new Runnable() {
@Override
public void run() {
boolean suc = upload();
}
});
}
}
我想用Mockito对这个功能进行单元测试,我试过了:
MyPool mockMyPool = Mockito.mock(MyPool.class);
ThreadPool mockThreadPool = Mockito.mock(ThreadPool.class);
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
when(mockMyPool.getThreadPool()).thenReturn(mockThreadPool);
MyService service = new MyService();
// run the method under test
service.uploadData();
// set the runnableCaptor to hold your callback
verify(mockThreadPool).execute(runnableCaptor.capture());
但我错了:
org.mockito.exceptions.verification.WantedButNotInvoked:
Wanted but not invoked:
threadPool.execute(
<Capturing argument>
);
为什么我会出现这个错误,如何用Mockito对uploadData()函数进行单元测试?
好吧,我自己想好了一种方法,因为MyPool
是一个单例。我添加了一个公共函数setInstance(mockedInstance)
来将模拟实例传递给MyPool
。然后,它就起作用了。我知道这有点"脏",但如果你有更好的解决方案,请告诉我。谢谢
除了保留MyPool或ThreadPool字段的DI方法外,您还可以进行一些重构,以便在方法中注入依赖项:
public class MyService {
...
public void uploadData() {
uploadData(MyPool.getInstance().getThreadPool());
}
/** Receives an Executor for execution. Package-private for testing. */
void uploadData(Executor executor) {
executor.execute(new Runnable() {
@Override public void run() {
boolean suc = upload();
}
});
}
}
这可能更干净,因为它将ThreadPool降低到了所需的抽象级别(Executor),这意味着你只模拟一个方法接口,而不是ThreadPool(我认为它与ThreadPoolService有关;否则,你也可以接受ThreadPool)。根据官方说法,你的uploadData()
是未经测试的,但你可以轻松彻底地测试uploadData(Executor)
或uploadData(ThreadPool)
,这是最有可能断裂的运动部件。
包私有技巧确实依赖于你的代码和测试在同一个包中,尽管它们可能在不同的源文件夹中;或者,您可以将ThreadPool-receiving调用作为公共API的一部分,这将允许以后有更大的灵活性。