Rob*_*bin 6 java unit-testing decorator mockito
假设我有以下课程
public abstract class Foo{
public int bar(){
//implementation
}
public abstract int bar2();
}
Run Code Online (Sandbox Code Playgroud)
和一个基类,以便更容易为此类编写装饰器
public class FooWrapper{
private final Foo delegate;
protected FooWrapper( Foo delegate ){
this.delegate = delegate;
}
@Override
public int bar(){
return delegate.bar()
}
@Override
public int bar2(){
return delegate.bar2();
}
}
Run Code Online (Sandbox Code Playgroud)
该类FooWrapper允许您编写装饰器,Foo只覆盖您需要的方法.
现在我想编写一个测试FooWrapper,检查是否默认委派所有方法.当然我可以写一些类似的东西
@Test
public void barShouldBeDelegated(){
Foo delegate = Mockito.mock( Foo.class );
FooWrapper wrapper = new FooWrapper( delegate );
wrapper.bar();
Mockito.verify( delegate ).bar();
}
Run Code Online (Sandbox Code Playgroud)
但这需要我在每次添加方法时添加新的测试方法Foo.我希望每次添加一个Foo我忘记覆盖和委托的方法时,测试都会失败FooWrapper.
我试图使用反射,允许我调用每个方法,但我不知道如何检查该方法是否实际委派.请参阅以下代码片段,了解我的想法:
@Test
public void testAllMethodsAreDelegated() throws Exception{
Foo delegate = mock(Foo.class);
FooWrapper wrapper = new FooWrapper(delegate);
Class<?> clazz = wrapper.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] arguments = new Object[parameterTypes.length];
for (int j = 0; j < arguments.length; j++) {
arguments[j] = Mockito.mock(parameterTypes[j]);
}
method.invoke(wrapper, arguments);
// ?? how to verify whether the delegate is called
// Mockito.verify( delegate ).??? does not work
// as I cannot specify the method by name
}
}
}
Run Code Online (Sandbox Code Playgroud)
是否有可能编写这样的测试.请注意,我可以使用的唯一模拟框架是Mockito.
小智 6
这段代码似乎可以解决问题.如果我向Foo添加一个方法并且不在FooWrapper中包含它,则测试失败.
FooWrapper wrapper = new FooWrapper(delegate);
Foo delegate = Mockito.mock(Foo.class);
// For each method in the Foo class...
for (Method fooMethod : Foo.class.getDeclaredMethods()) {
boolean methodCalled = false;
// Find matching method in wrapper class and call it
for (Method wrapperMethod : FooWrapper.class.getDeclaredMethods()) {
if (fooMethod.getName().equals(wrapperMethod.getName())) {
// Get parameters for method
Class<?>[] parameterTypes = wrapperMethod.getParameterTypes();
Object[] arguments = new Object[parameterTypes.length];
for (int j = 0; j < arguments.length; j++) {
arguments[j] = Mockito.mock(parameterTypes[j]);
}
// Invoke wrapper method
wrapperMethod.invoke(wrapper, arguments);
// Ensure method was called on delegate exactly once with the correct arguments
fooMethod.invoke(Mockito.verify(delegate, Mockito.times(1)), arguments);
// Set flag to indicate that this foo method is wrapped properly.
methodCalled = true;
}
}
assertTrue("Foo method '" + fooMethod.getName() + "' has not been wrapped correctly in Foo wrapper", methodCalled);
}
Run Code Online (Sandbox Code Playgroud)
这里的代码中缺少的关键是
fooMethod.invoke(Mockito.verify(delegate, Mockito.times(1)), arguments);
Run Code Online (Sandbox Code Playgroud)
它可能看起来有点奇怪,但这是有效的,因为它以Mockito所期望的相同顺序调用事物:首先Mockito.verify(delegate)调用(在内部启动Mockito验证),然后调用该方法.类似的非反射调用看起来像Mockito.verify(delegate).foo().使用此"原因"来帮助调整代码以适应不同的用例,而不会破坏测试的验证方式.
需要注意的是,我会在每个循环的开头添加一个检查,迭代getDeclaredMethods()的结果.此方法返回所有方法,无论它们是公共的,私有的,受保护的等.尝试访问不可访问的方法会引发异常.您可以使用Method.isAccessible()来检查这一点.