调用mockito中的模拟对象

时间:2013-06-25 14:57:21

标签: unit-testing mockito

我有一个非常复杂的方法,我想测试它的行为(使用Mockito和JUnit)。这个方法将一个对象(让我们称之为类型State)作为输入,并且应该考虑一些不同的状态变量来决定它的输出。

作为示例,考虑以下规范(sState类的模拟):

  1. 如果设置了s.varOne,则返回其值。
  2. 否则,如果设置了s.varTwo,则返回。
  3. 否则,如果设置了s.varThree,请致电s.update(s.varThree),然后返回s.varOne,现在会有一个值(即使它没有在第1阶段。)
  4. 否则,抛出错误。
  5. 为了正确测试案例3,我想设置s对象,以便s.varOnes.varTwo都不会开始,但是如果(并且仅当!)sut调用s.update(s.varThree),之后s.varOne返回一些内容。

    有没有一种很好的方法可以在Mockito中设置此行为?

    我考虑为s.varOne设置一些返回值链,然后验证调用的顺序是否与输出的顺序相对应(以及sut的返回值是否正确,当然),但这感觉很脏;如果我然后改变方法以其他方式计算其返回值,它调用s.varOne不同的次数但不改变其输出,那么即使功能相同,测试也会失败。 / p>

    我理想的解决方案是一种方法,我可以为模拟对象添加一些“延迟”设置,该设置在sut调用s.update()方法时运行,但我无法找到实现该方法的方法

1 个答案:

答案 0 :(得分:2)

您可以在此处模拟状态更改,这是一个不错的选择和更好的选择。最好的选择,如上面的 tieTYT 注释,只是为了解开State的一般契约:State是否真的有意义是可变的,并且有自我意识-mutating方法不是简单的setter?

好的选择(有时)是创建一个一次性子类 - 或者更好的是State的接口实现。

@Test public void yourTest() {
  SystemUnderTest sut = createSystemUnderTest();
  State state = new State() {
    @Override public void update() {
      this.varOne = 42;
    }
  }
  // rest of your test
}

那时,你根本不需要Mockito。但要注意:如果State有副作用或者难以实例化,这可能会有点棘手。你可以提取一个接口,这需要你把你的字段包装在getter中;你也可以使State成为一个命名的抽象类,然后传递mock(MyAbstractState.class, CALLS_REAL_METHODS),但是如果你认为没有初始化程序实际上在那个假实例上运行,那么这会变得特别毛茸茸,因此这些字段都是零或null。如果不简单,不要浪费时间将方形钉子钉入圆孔。

模拟的一种更常见的模式是使用自定义Answer,您可以在其中以类型安全为代价执行任意代码。这是一个示例,假设update为空,varThree是整数:

@Test public void yourTest() {
  SystemUnderTest sut = createSystemUnderTest();
  final State s = Mockito.mock(State.class);
  doAnswer(new Answer<Void>() {
    @Override public Void answer(InvocationOnMock invocation) {
      int actualVarThree = (int) invocation.getArguments()[0];
      assertEquals(EXPECTED_VAR_THREE, actualVarThree);
      s.varOne = actualVarThree;
      return null;
    }
  }).when(s).update(anyInt());
  // rest of your test
}

请注意,参数数组是Object[],因此强制转换是必要且有点危险,但是当您调用模拟方法时,您可以同步进行各种断言和修改。

希望有所帮助!