Mockito isNotNull传递null

时间:2014-07-18 18:26:20

标签: mockito

提前感谢您的帮助 - 我是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));

再次感谢

2 个答案:

答案 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的验证码。通常会发生这种情况,因为deleteLogfinal: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存在。
  • 您也可以跳过创建日志,因为您正在测试交互,而不是状态。
  • 你对MonitoringController的实现更加紧密:你不能简单地测试它是否持有它的总契约。
  • Mockito可以限制个人互动,但让他们保持一致很难。如果您希望LogRepository.count返回2,直到您拨打delete,然后返回0,这在Mockito中难以表达。这就是编写实现来表示有状态对象并让Mockito模拟无状态服务接口的原因。