有一个名为Foo
的界面。
interface Foo {
void add(Object key, Object value);
Object get(Object key);
void someOtherMethodUnessentialToTesting();
}
在测试中有一个名为MockFoo
的实现以“默认”方式实现大多数方法(什么都不做,返回null
,基本上只实现它们以便编译)。但是,它实现了一对来提供真正的功能,它们是从Map
插入和读取的方法。 (如果最后一点不重要,我会使用Mockito模拟,甚至不会问。)
// The current "bad mock"
class MockFoo implements Foo {
Map<Object, Object> map = new ...
@Override
void add(Object key, Object value) {
map.put(key, value);
}
@Override
Object get(Object key) {
map.get(key);
}
@Override
void someOtherMethodUnessentialToTesting()
{}
}
问题在于,因为这不是Mockito模拟,每次界面更改时都必须更新测试。是的,人们应该更好地检查以修复他们更改的界面的所有实现,但在我看来,确实不应该是测试中的实现。
我很困惑如何解决这个问题。我的直觉是使它抽象并只实现那些方法,然后以某种方式模拟它,以便它在需要时调用那些“真正的”方法。我读到Mockito有一个thenDoRealMethod()
存根,但这只是为了返回值,所以它不适用于void方法。
// My abstract class I was trying to stub somehow
abstract class FooForTest implements Foo {
Map<Object, Object> map = new ...
@Override
void add(Object key, Object value) {
map.put(key, value);
}
@Override
Object get(Object key) {
map.get(key);
}
}
我意识到这可能是一个设计问题,在真实代码中添加AbstractFoo
可能是最好的(因为添加和获取不会真正改变)但我更好奇是否有办法只需修改测试代码就可以解决这个问题。
答案 0 :(得分:1)
使用像this SO answer这样的技术,你可以使用CALLS_REAL_METHODS作为默认答案 - 或者,如你的建议,你可以使用默认答案(RETURNS_DEFAULTS)并单独存根某些方法来调用你的假。因为你想要通过Mockito的行为,我会推荐后者。
thenCallRealMethod
方法的void
相当于:doCallRealMethod
。您需要从do
开始,因为when(T value)
没有值[并随后忽略]。
abstract class FooForTest implements Foo {
public static Foo create() {
FooForTest mockFoo = mock(FooForTest.class);
mockFoo.map = new HashMap<>();
when(mockFoo.get(any())).thenCallRealMethod();
doCallRealMethod().when(mockFoo).add(any(), any());
return mockFoo;
}
Map<Object, Object> map = new ...
@Override
void add(Object key, Object value) {
map.put(key, value);
}
@Override
Object get(Object key) {
map.get(key);
}
}
没有复杂的反思,我想不出任何方法可以自动让Mockito为未实现的方法调用真正的方法。但是,如果您要为此编写答案,则可以将其用于所有方法,方法是将其传递给mock
。您还需要在create
方法中显式初始化字段,因为模拟不是真实对象,并且无法正确初始化。
另一种选择是使用空方法存根,而使用spy()
代替;这将解决一些初始化问题。
这种技术被称为部分模拟,并且要注意它可以充当code smell - 具体来说,被测试的类违反了单一责任原则。在任何情况下,出于所有这些原因,在向广泛实现的接口添加方法时应该非常小心,并且考虑AbstractFoo
(或完全实现的FakeFoo
)作为后来的合理升级。