提前感谢您的帮助我是一个新的mockito,但已经花了最后一天看的例子和文档,但还没有能够找到一个解决我的问题,所以希望这不是一个太愚蠢的问题。
我想验证deleteLog()调用deleteLog(Path) NUM_LOGS_TO_DELETE的次数,每个路径标记为删除。我不关心模拟中的路径是什么(因为我不想去文件系统、集群等进行测试),所以我验证deleteLog是否以任何非空path作为参数调用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));
再次感谢
我从来没有使用过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);
}
isNotNull是一个返回null的方法,这是故意的。Mockito匹配器通过副作用工作,所以所有匹配器或多或少都期望返回假值,如null
或0,而不是在Mockito框架内的堆栈上记录它们的期望。
意想不到的部分是您的MonitoringController.deleteLog
实际上调用您的代码,而不是调用Mockito的验证代码。通常情况下,这是因为deleteLog
是final
: Mockito通过子类(实际上是动态代理)工作,并且由于final
禁止子类化,编译器基本上跳过了虚拟方法查找,并将调用直接内联到实现而不是Mockito的mock。仔细检查您试图存根或验证的方法不是final
,因为您指望它们在您的测试中不表现为final
。
在测试中直接在mock上调用方法几乎是不正确的;如果这是一个MonitoringControllerTest,你应该使用一个真正的MonitoringController并模拟它的依赖关系。我希望您的mockController.deleteLogs()
只是代表您的实际测试代码,在那里您执行一些其他组件,依赖于和与 MonitoringController交互。
大多数测试根本不需要mock。假设你有这样一个类:
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为你做一个mock:
@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的一些优点和局限性:
- 你不再需要实现来保持状态。你甚至不需要
- 您也可以跳过创建日志,因为您正在测试交互,而不是状态。
- 你更紧密地绑定到MonitoringController的实现:你不能简单地测试它是否坚持它的一般合同。 Mockito可以保留单个交互,但要保持一致是很困难的。如果您希望
getLogCount
的存在。LogRepository.count
在调用delete
之前返回2,然后返回0,那么在Mockito中很难表达。这就是为什么写伪实现来表示有状态对象,而把mock留给无状态服务接口的原因。