我想测试一个抽象类。当然,我可以手动编写一个从该类继承的模拟。
我可以使用mock框架(我使用Mockito)而不是手工制作我的mock吗?如何?
我想测试一个抽象类。当然,我可以手动编写一个从该类继承的模拟。
我可以使用mock框架(我使用Mockito)而不是手工制作我的mock吗?如何?
当前回答
您可以通过使用间谍来实现这一点(尽管使用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()));
其他回答
尝试使用自定义答案。
例如:
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);
}
}
它将为抽象方法返回mock,为具体方法调用real方法。
模拟框架的设计目的是为了更容易模拟出正在测试的类的依赖关系。当您使用模拟框架模拟一个类时,大多数框架都会动态地创建一个子类,并将方法实现替换为用于检测方法何时被调用并返回假值的代码。
在测试抽象类时,您希望执行Subject Under Test (SUT)的非抽象方法,因此模拟框架并不是您想要的。
造成这种困惑的部分原因是,您所链接的问题的答案要求手工创建一个从抽象类扩展而来的模拟。我不认为这样的课程是mock。mock是一个类,它被用作依赖项的替代品,它是按照期望进行编程的,并且可以查询这些期望是否得到满足。
相反,我建议在测试中定义抽象类的非抽象子类。如果这导致了太多的代码,那么这可能表明您的类难以扩展。
另一种解决方案是使您的测试用例本身是抽象的,使用抽象的方法来创建SUT(换句话说,测试用例将使用模板方法设计模式)。
PowerMock的Whitebox.invokeMethod(..)在这种情况下很方便。
您可以实例化一个匿名类,注入您的模拟,然后测试该类。
@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);
}
}
请记住,必须保护抽象类ClassUnderTest的属性myDependencyService的可见性。
如果你只是需要测试一些具体方法而不涉及任何抽象,你可以使用CALLS_REAL_METHODS(见Morten的回答),但是如果被测试的具体方法调用一些抽象,或未实现的接口方法,这将不起作用——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方法,你需要使用encallrealmethod()来代替,例如:
when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();
否则Mockito将报怨“检测到未完成的存根”。