模拟抽象的其余部分 class 但调用其中的真实方法?
Mock the rest of an abstract class but call the real methods in it?
有一些接口叫做 Foo
。
interface Foo {
void add(Object key, Object value);
Object get(Object key);
void someOtherMethodUnessentialToTesting();
}
测试中有一个名为 MockFoo
的实现,它以 "default" 的方式实现了大多数方法(什么都不做,返回 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 模拟,每次界面更改时都必须更新测试。是的,人们应该更好地检查以修复所有实现他们更改了界面,但我认为 确实 不应该首先在测试中实现。
我对如何解决这个问题感到困惑。我的直觉是将其抽象化并仅实现那些方法,然后以某种方式模拟它,以便它在需要时调用那些 "real" 方法。我读到 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
可能是最好的(因为添加和获取不会真正改变)但我更好奇是否有只需修改测试代码即可一劳永逸地解决此问题。
使用 this SO answer 中的技术,您可以使用 CALLS_REAL_METHODS 作为默认答案,或者,如您所建议的,您可以使用默认答案 (RETURNS_DEFAULTS) 并单独使用存根某些方法来调用你的假货。因为你想让 Mockito 的行为表现出来,所以我推荐后者。
void
方法有一个等价于 thenCallRealMethod
的方法: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只为未实现的方法调用真正的方法。但是,如果您要为此编写一个 Answer,则可以通过将其传递到对 mock
的调用来将其用于所有方法。您还需要在 create
方法中显式初始化您的字段,因为模拟不是真实对象并且不会被正确初始化。
另一种选择是使用空方法存根,并使用 spy()
代替;这将解决一些初始化问题。
此技术称为 部分模拟,请注意它可以充当 code smell——具体来说,被测 class 违反了单一职责原则。出于所有这些原因,在任何情况下,在向广泛实现的接口添加方法时都应该非常小心,并考虑 AbstractFoo
(或完全实现的 FakeFoo
)作为以后的明智升级。
有一些接口叫做 Foo
。
interface Foo {
void add(Object key, Object value);
Object get(Object key);
void someOtherMethodUnessentialToTesting();
}
测试中有一个名为 MockFoo
的实现,它以 "default" 的方式实现了大多数方法(什么都不做,返回 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 模拟,每次界面更改时都必须更新测试。是的,人们应该更好地检查以修复所有实现他们更改了界面,但我认为 确实 不应该首先在测试中实现。
我对如何解决这个问题感到困惑。我的直觉是将其抽象化并仅实现那些方法,然后以某种方式模拟它,以便它在需要时调用那些 "real" 方法。我读到 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
可能是最好的(因为添加和获取不会真正改变)但我更好奇是否有只需修改测试代码即可一劳永逸地解决此问题。
使用 this SO answer 中的技术,您可以使用 CALLS_REAL_METHODS 作为默认答案,或者,如您所建议的,您可以使用默认答案 (RETURNS_DEFAULTS) 并单独使用存根某些方法来调用你的假货。因为你想让 Mockito 的行为表现出来,所以我推荐后者。
void
方法有一个等价于 thenCallRealMethod
的方法: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只为未实现的方法调用真正的方法。但是,如果您要为此编写一个 Answer,则可以通过将其传递到对 mock
的调用来将其用于所有方法。您还需要在 create
方法中显式初始化您的字段,因为模拟不是真实对象并且不会被正确初始化。
另一种选择是使用空方法存根,并使用 spy()
代替;这将解决一些初始化问题。
此技术称为 部分模拟,请注意它可以充当 code smell——具体来说,被测 class 违反了单一职责原则。出于所有这些原因,在任何情况下,在向广泛实现的接口添加方法时都应该非常小心,并考虑 AbstractFoo
(或完全实现的 FakeFoo
)作为以后的明智升级。