我想测试一个抽象类。当然,我可以manually write a mock继承自班级。
我可以使用模拟框架(我使用的是Mockito)而不是手工制作我的模拟吗?怎么样?
答案 0 :(得分:297)
以下建议让您在不创建“真实”子类的情况下测试抽象类 - 模拟是的子类。
使用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());
}
}
注意:此解决方案的优点在于,您不会拥有来实现抽象方法,只要它们永远不会被调用。
在我看来,这比使用间谍更简洁,因为间谍需要一个实例,这意味着你必须创建抽象类的可实例化的子类。
答案 1 :(得分:66)
如果你只需要测试一些具体方法而不涉及任何摘要,你可以使用CALLS_REAL_METHODS
(参见Morten's answer),但如果测试中的具体方法调用了一些摘要,或未实现的接口方法,这将无法正常工作 - Mockito会抱怨“无法在java接口上调用真正的方法。”
(是的,这是一个糟糕的设计,但是一些框架,例如Tapestry 4,有点强迫它。)
解决方法是颠倒这种方法 - 使用普通的模拟行为(即,所有内容都被模拟/存根)并使用doCallRealMethod()
显式调用测试中的具体方法。 E.g。
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();
}
}
已更新以添加:
对于非空方法,您需要使用thenCallRealMethod()
代替,例如:
when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();
否则Mockito会抱怨“检测到未完成的短截。”
答案 2 :(得分:16)
你可以通过使用间谍来实现这一目标(尽管使用最新版本的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()));
答案 3 :(得分:13)
模拟框架旨在简化模拟您正在测试的类的依赖关系。当您使用模拟框架来模拟一个类时,大多数框架会动态创建一个子类,并将该方法实现替换为用于检测何时调用方法并返回伪值的代码。
在测试抽象类时,您希望执行主题测试(SUT)的非抽象方法,因此模拟框架不是您想要的。
部分困惑在于你所链接的问题的答案是手工制作一个从你的抽象类扩展的模拟。我不会把这样的课称为模拟。模拟是一个用作替代依赖的类,按期望编程,可以查询是否满足这些期望。
相反,我建议在测试中定义抽象类的非抽象子类。如果这会导致代码太多,那么这可能表示您的课程很难扩展。
另一种解决方案是使您的测试用例本身抽象化,使用抽象方法创建SUT(换句话说,测试用例将使用Template Method设计模式)。
答案 4 :(得分:8)
尝试使用自定义答案。
例如:
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);
}
}
它将返回抽象方法的模拟,并将为具体方法调用实际方法。
答案 5 :(得分:5)
真正让我对模拟抽象类感觉不好的事实是,默认构造函数YourAbstractClass()都没有被调用(模拟中缺少super())也似乎没有任何方法可以在Mockito中默认初始化模拟属性(例如列出具有空ArrayList或LinkedList的属性。
我的抽象类(基本上是生成类源代码)不提供列表元素的依赖项setter注入,也不提供初始化列表元素的构造函数(我试图手动添加)。
只有类属性使用默认初始化: private List dep1 = new ArrayList; private List dep2 = new ArrayList
因此没有办法在不使用真实对象实现(例如,单元测试类中的内部类定义,重写抽象方法)和监视真实对象(进行正确的字段初始化)的情况下模拟抽象类。
太糟糕了,只有PowerMock会在这里提供更多帮助。
答案 6 :(得分:2)
假设您的测试类与测试中的类位于同一个包中(在不同的源根下),您只需创建模拟:
YourClass yourObject = mock(YourClass.class);
并调用您想要测试的方法,就像调整任何其他方法一样。
您需要为调用超级方法的任何具体方法的期望调用每个方法提供期望 - 不确定如何使用Mockito执行此操作,但我相信可以使用EasyMock。
所有这一切都是创建YouClass
的具体实例,并为您节省了为每个抽象方法提供空实现的工作。
顺便说一句,我经常发现在我的测试中实现抽象类很有用,它作为我通过其公共接口测试的示例实现,尽管这取决于抽象类提供的功能。 / p>
答案 7 :(得分:2)
您可以在测试中使用匿名类扩展抽象类。 例如(使用Junit 4):
private AbstractClassName classToTest;
@Before
public void preTestSetup()
{
classToTest = new AbstractClassName() { };
}
// Test the AbstractClassName methods.
答案 8 :(得分:0)
您可以实例化一个匿名类,注入您的模拟,然后测试该类。
@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
。
答案 9 :(得分:0)
在这种情况下,Whitebox.invokeMethod(..)可能很方便。
答案 10 :(得分:0)
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();
// ...
}
}
缺点是,如果需要构造函数参数,则无法使用。
答案 11 :(得分:0)
class Dependency{
public void method(){};
}
public abstract class My {
private Dependency dependency;
public abstract boolean myAbstractMethod();
public void myNonAbstractMethod() {
// ...
dependency.method();
}
}
@RunWith(MockitoJUnitRunner.class)
public class MyTest {
@InjectMocks
private My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
// we can mock dependencies also here
@Mock
private Dependency dependency;
@Test
private void shouldPass() {
// can be mock the dependency object here.
// It will be useful to test non abstract method
my.myNonAbstractMethod();
}
}