提前感谢您的帮助 - 我是mockito的新手,但是最后一天看了一些示例和文档,但是找不到我的问题的解决方案,所以希望这不是一个愚蠢的问题。
我想验证deleteLogs()调用deleteLog(Path)NUM_LOGS_TO_DELETE次,每个路径标记为删除。我不关心模拟中的路径是什么(因为我不想去测试的文件系统,集群等)所以我验证deleteLog被称为NUM_LOGS_TO_DELETE次,任何非空路径为一个参数。然而,当我逐步执行时, deleteLog会传递一个空参数 - 这会导致NullPointerException(基于我继承的代码的行为)。
也许我做错了什么,但是验证并且isNotNull的使用看起来很简单......这是我的代码:
MonitoringController mockController = mock(MonitoringController.class);
// Call the function whose behavior I want to verify
mockController.deleteLogs();
// Verify that mockController called deleteLog the appropriate number of times
verify(mockController, Mockito.times(NUM_LOGS_TO_DELETE)).deleteLog(isNotNull(Path.class));
再次感谢
答案 0 :(得分:1)
我从来没有使用过isNotNull参数,所以我不能说你的代码出了什么问题 - 我总是使用ArgumentCaptor。基本上你告诉它要查找什么类型的参数,它捕获它们,然后在调用之后你可以断言你正在寻找的值。试试下面的代码:
ArgumentCaptor<Path> pathCaptor = ArgumentCaptor.forClass(Path.class);
verify(mockController, Mockito.times(NUM_LOGS_TO_DELETE)).deleteLog(pathCaptor.capture());
for (Path path : pathCaptor.getAllValues()) {
assertNotNull(path);
}
答案 1 :(得分:0)
事实证明, isNotNull 是一个返回null的方法,这是故意的。 Mockito matchers work via side effects,因此所有匹配器都会或多或少地期望返回虚拟值,如null
或0,而是在Mockito框架内的堆栈上记录他们的期望。
意外部分原因是您的MonitoringController.deleteLog
实际上是在调用您的代码,而不是调用Mockito的验证码。通常会发生这种情况,因为deleteLog
是final
:Mockito通过子类(实际上是动态代理)工作,并且因为final
禁止子类化,编译器基本上会跳过虚拟方法查找并直接将调用内联到实施而不是Mockito的模拟。仔细检查您尝试存根或验证的方法不是final
,因为您指望它们在测试中不会表现为final
。
在测试中直接调用模拟方法几乎永远不正确;如果这是一个MonitoringControllerTest,你应该使用一个真正的MonitoringController并模拟其依赖项。我希望您的mockController.deleteLogs()
只是代表您的实际测试代码,您可以在其中使用依赖和与 MonitoringController交互的其他组件。
大多数测试根本不需要嘲笑。假设你有这个课程:
class MonitoringController {
private List<Log> logs = new ArrayList<>();
public void deleteLogs() {
logs.clear();
}
public int getLogCount() {
return logs.size();
}
}
然后这将是一个不使用Mockito的有效测试:
@Test public void deleteLogsShouldReturnZeroLogCount() {
MonitoringController controllerUnderTest = new MonitoringController();
controllerUnderTest.logSomeStuff(); // presumably you've tested elsewhere
// that this works
controllerUnderTest.deleteLogs();
assertEquals(0, controllerUnderTest.getLogCount());
}
但您的监控控制器也可能如下所示:
class MonitoringController {
private final LogRepository logRepository;
public MonitoringController(LogRepository logRepository) {
// By passing in your dependency, you have made the creator of your class
// responsible. This is called "Inversion-of-Control" (IoC), and is a key
// tenet of dependency injection.
this.logRepository = logRepository;
}
public void deleteLogs() {
logRepository.delete(RecordMatcher.ALL);
}
public int getLogCount() {
return logRepository.count(RecordMatcher.ALL);
}
}
突然间,测试代码可能并不那么容易,因为它不会保持自己的状态。要使用与上面相同的测试,您需要一个有效的LogRepository。您可以编写一个FakeLogRepository来保存内存,这是一个很好的策略,或您可以使用Mockito为您制作模拟:
@Test public void deleteLogsShouldCallRepositoryDelete() {
LogRepository mockLogRepository = Mockito.mock(LogRepository.class);
MonitoringController controllerUnderTest =
new MonitoringController(mockLogRepository);
controllerUnderTest.deleteLogs();
// Now you can check that your REAL MonitoringController calls
// the right method on your MOCK dependency.
Mockito.verify(mockLogRepository).delete(Mockito.eq(RecordMatcher.ALL));
}
这显示了Mockito的一些好处和局限性:
getLogCount
存在。LogRepository.count
返回2,直到您拨打delete
,然后返回0,这在Mockito中难以表达。这就是编写伪实现来表示有状态对象并让Mockito模拟无状态服务接口的原因。