我有一个非常复杂的方法,我想测试它的行为(使用Mockito和JUnit)。这个方法将一个对象(让我们称其类型为State
)作为输入,并且应该考虑一些不同的状态变量来决定其输出。
例如,考虑以下规范(s
是State
类的模拟):
- 如果设置了
s.varOne
,则返回其值 - 否则,如果设置了
s.varTwo
,则返回该值 - 否则,如果设置了
s.varThree
,则调用s.update(s.varThree)
,然后返回s.varOne
,它现在将有一个值(即使在第1阶段没有) - 否则,抛出错误
为了正确测试用例3,我想设置s
对象,以便s.varOne
和s.varTwo
一开始都是未设置的,但如果(且仅当!)sut调用s.update(s.varThree)
,那么在那之后s.varOne
会返回一些东西。
有没有一种好的方法可以在Mockito中设置这种行为?
我曾考虑为s.varOne
设置一些返回值链,然后验证调用的顺序是否与输出的顺序相对应(当然还有sut的返回值是否正确),但这感觉很糟糕;如果我随后更改该方法以其他方式计算其返回值,即调用s.varOne
不同次数但不更改其输出,那么即使功能相同,测试也将失败。
我的理想解决方案是,我可以为mock对象添加一些"延迟"设置,当sut调用s.update()
方法时会运行该设置,但我无法找到实现这一点的方法。
这里有两个模拟状态更改的选项,一个是好的选项,另一个是更好的选项。正如tieTYT上面所指出的,最好的选择是解开State
的一般契约:State
是可变的,并且拥有不是简单的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时,这会变得特别棘手。如果这不简单,不要浪费时间把一个方桩钉进一个圆孔。
mock的一种更常见的模式是使用自定义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[]
,因此强制转换是必要的,而且有点危险,但在调用模拟方法时,可以同步进行各种断言和修改。
希望能有所帮助!