Mockito isNotNull passes null



提前感谢您的帮助我是一个新的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的验证代码。通常情况下,这是因为deleteLogfinal: 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的一些优点和局限性:

    你不再需要实现来保持状态。你甚至不需要getLogCount的存在。
  • 您也可以跳过创建日志,因为您正在测试交互,而不是状态。
  • 你更紧密地绑定到MonitoringController的实现:你不能简单地测试它是否坚持它的一般合同。
  • Mockito可以保留单个交互,但要保持一致是很困难的。如果您希望LogRepository.count在调用delete之前返回2,然后返回0,那么在Mockito中很难表达。这就是为什么写实现来表示有状态对象,而把mock留给无状态服务接口的原因。

相关内容

  • 没有找到相关文章

最新更新