使用 Mockito 测试抽象类
-
23-08-2019 - |
题
我想测试一个抽象类。我当然可以 手动编写模拟 从类继承。
我可以使用模拟框架(我正在使用 Mockito)来完成此操作,而不是手工制作我的模拟吗?如何?
解决方案
下面的建议让我们在测试抽象类,而无需创建一个“真实”的子类 - 素的是的子类
使用Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS)
,然后嘲笑被调用的任何抽象方法。
示例:
public abstract class My {
public Result methodUnderTest() { ... }
protected abstract void methodIDontCareAbout();
}
public class MyTest {
@Test
public void shouldFailOnNullIdentifiers() {
My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
Assert.assertSomething(my.methodUnderTest());
}
}
请注意:该解决方案的好处是,你不这样做的的的实施抽象方法,只要它们永远不会调用
。在我的真实想法,这比使用间谍整洁,因为间谍需要一个实例,这意味着你必须创建抽象类的实例化的子类。
其他提示
如果您只需要测试一些具体方法而不接触任何抽象,您可以使用 CALLS_REAL_METHODS
(看 莫滕的回答),但是如果被测试的具体方法调用了一些抽象方法,或者未实现的接口方法,那么这将不起作用——Mockito 会抱怨“无法在 java 接口上调用真正的方法”。
(是的,这是一个糟糕的设计,但有些框架,例如Tapestry 4,有点强加给你。)
解决方法是扭转这种方法——使用普通的模拟行为(即,所有内容都被模拟/存根)并使用 doCallRealMethod()
显式调用被测试的具体方法。例如。
public abstract class MyClass {
@SomeDependencyInjectionOrSomething
public abstract MyDependency getDependency();
public void myMethod() {
MyDependency dep = getDependency();
dep.doSomething();
}
}
public class MyClassTest {
@Test
public void myMethodDoesSomethingWithDependency() {
MyDependency theDependency = mock(MyDependency.class);
MyClass myInstance = mock(MyClass.class);
// can't do this with CALLS_REAL_METHODS
when(myInstance.getDependency()).thenReturn(theDependency);
doCallRealMethod().when(myInstance).myMethod();
myInstance.myMethod();
verify(theDependency, times(1)).doSomething();
}
}
更新添加:
对于非 void 方法,您需要使用 thenCallRealMethod()
相反,例如:
when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();
否则 Mockito 会抱怨“检测到未完成的存根”。
您可以通过使用间谍(使用最新版本的的Mockito虽然1.8+)实现这一目标。
public abstract class MyAbstract {
public String concrete() {
return abstractMethod();
}
public abstract String abstractMethod();
}
public class MyAbstractImpl extends MyAbstract {
public String abstractMethod() {
return null;
}
}
// your test code below
MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));
模拟框架旨在更轻松地模拟您正在测试的类的依赖关系。当您使用模拟框架来模拟类时,大多数框架会动态创建子类,并将方法实现替换为用于检测何时调用方法并返回假值的代码。
在测试抽象类时,您希望执行被测主题 (SUT) 的非抽象方法,因此模拟框架不是您想要的。
部分困惑在于,您所链接的问题的答案是手工制作一个从您的抽象类扩展的模拟。我不会称这样的课程为模拟课程。模拟是一个类,用作依赖项的替代品,根据期望进行编程,并且可以查询以查看是否满足这些期望。
相反,我建议在测试中定义抽象类的非抽象子类。如果这会导致代码过多,则可能表明您的类难以扩展。
另一种解决方案是使测试用例本身变得抽象,并使用抽象方法来创建 SUT(换句话说,测试用例将使用 模板法 设计模式)。
尝试使用自定义的答案。
例如:
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class CustomAnswer implements Answer<Object> {
public Object answer(InvocationOnMock invocation) throws Throwable {
Answer<Object> answer = null;
if (isAbstract(invocation.getMethod().getModifiers())) {
answer = Mockito.RETURNS_DEFAULTS;
} else {
answer = Mockito.CALLS_REAL_METHODS;
}
return answer.answer(invocation);
}
}
将返回对抽象方法模拟和将调用真实方法的具体方法。
但真正让我感到很难过嘲讽抽象类是事实,无论是默认的构造函数YourAbstractClass()被调用(缺少超()在模拟),也似乎还有是在的Mockito任何方式默认初始化模拟特性(例如列表属性与空的ArrayList或链表)。
我的抽象类(基本类源代码获取生成)不提供依赖setter注入为列表中的元素,也不在那里它初始化的列表元素的构造方法(我试图手动添加)。
只有类属性使用缺省初始化: 私人列表DEP1 =新的ArrayList; 私人列表DEP2 =新的ArrayList
,所以没有办法来模拟一个抽象类,而无需使用一个真实对象执行(在单元测试类e.g内类的定义,压倒一切的抽象方法)和间谍真实对象(其不正确的字段初始化)。
太糟糕了,只有PowerMock将有助于进一步这里。
假设你的测试类是在同一封装(不同的源根目录下),为你的类被测可以简单地创建模拟:
YourClass yourObject = mock(YourClass.class);
和打电话给你想测试就像你的方法对其他任何方法。
您需要为每个被称为与任何具体的方法调用父类方法的期望方法的期待 - 不知道你会怎么做,与的Mockito,但我相信这是可能与EasyMock的
。这一切正在做的是创造YouClass
的具体实例,并节省您提供的每个抽象方法的空实现的努力。
顺便说一句,我经常发现,以实现我的测试中,它作为一个示例实现,我通过它的公共接口测试抽象类是有用的,但是这并不依赖于抽象类提供的功能。
您可以在您的测试匿名类扩展抽象类。 例如(使用JUnit 4):
private AbstractClassName classToTest;
@Before
public void preTestSetup()
{
classToTest = new AbstractClassName() { };
}
// Test the AbstractClassName methods.
您可以实例化一个匿名类,注入你的嘲笑,然后测试类。
@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {
private ClassUnderTest classUnderTest;
@Mock
MyDependencyService myDependencyService;
@Before
public void setUp() throws Exception {
this.classUnderTest = getInstance();
}
private ClassUnderTest getInstance() {
return new ClassUnderTest() {
private ClassUnderTest init(
MyDependencyService myDependencyService
) {
this.myDependencyService = myDependencyService;
return this;
}
@Override
protected void myMethodToTest() {
return super.myMethodToTest();
}
}.init(myDependencyService);
}
}
记请使能见度必须protected
为抽象类myDependencyService
的属性ClassUnderTest
。
Whitebox.invokeMethod(..)可以是在这种情况下很方便。
允许的Mockito嘲笑抽象类由@Mock
注释的手段:
public abstract class My {
public abstract boolean myAbstractMethod();
public void myNonAbstractMethod() {
// ...
}
}
@RunWith(MockitoJUnitRunner.class)
public class MyTest {
@Mock(answer = Answers.CALLS_REAL_METHODS)
private My my;
@Test
private void shouldPass() {
BDDMockito.given(my.myAbstractMethod()).willReturn(true);
my.myNonAbstractMethod();
// ...
}
}
的缺点是,如果需要的构造参数它不能被使用。