我想测试一个抽象类。当然,我可以手动编写一个继承该类的模拟。
我能否使用一个mocking框架(我正在使用Mockito)来代替手工制作我的mock?如何操作?
以下建议可以让您测试抽象类,而无需创建一个“真正的”子类 - 模拟对象就是子类,只是部分模拟。
使用 Mockito.mock(My.class, Answers.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, Answers.CALLS_REAL_METHODS);
Assert.assertSomething(my.methodUnderTest());
}
}
注意:这个解决方案的优美之处在于您不需要实现抽象方法。 CALLS_REAL_METHODS
会使所有真实方法按原样运行,只要您不在测试中对它们进行桩操作。
在我看来,这比使用 spy 更加整洁,因为 spy 需要一个实例,这意味着您必须创建一个可实例化的抽象类子类。
CALLS_REAL_METHODS
(请参见Morten's answer),但是如果要测试的具体方法调用了一些抽象或未实现的接口方法,则这种方法将无效--Mockito会抱怨“无法在Java接口上调用真实方法”。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();
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()));
Mocking框架旨在使您更轻松地模拟测试类的依赖项。当使用mocking框架模拟类时,大多数框架会动态创建一个子类,并用用于检测方法调用并返回虚假值的代码替换方法实现。
当测试抽象类时,您希望执行SUT(Subject Under Test)的非抽象方法,因此mocking框架并不是您想要的。
部分混淆是因为您链接的问题的答案建议手动制作扩展自抽象类的模拟。我不会称这样的类为mock。mock是一种用作依赖项替代品的类,编程具有期望,并且可以查询以查看是否满足这些期望。
相反,我建议在测试中定义抽象类的非抽象子类。如果这导致了太多的代码,那么这可能表明您的类很难扩展。
另一种解决方案是将您的测试用例本身设置为抽象,具有用于创建SUT的抽象方法(换句话说,测试用例将使用Template Method设计模式)。
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);
}
}
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();
}
}
YourAbstractClass()
(在模拟中缺少super()
),也似乎没有任何方法在Mockito中默认初始化Mock属性(例如使用空的ArrayList
或LinkedList
初始化List
属性)。private List<MyGenType> dep1 = new ArrayList<MyGenType>();
private List<MyGenType> dep2 = new ArrayList<MyGenType>();
没有办法在不使用真实对象实现(例如单元测试类中的内部类定义,重写抽象方法)和对真实对象进行间谍操作(以进行适当字段初始化)的情况下模拟抽象类。
很遗憾,只有PowerMock可以进一步帮助解决这个问题。
private AbstractClassName classToTest;
@Before
public void preTestSetup()
{
classToTest = new AbstractClassName() { };
}
// Test the AbstractClassName methods.
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();
// ...
}
}
YourClass yourObject = mock(YourClass.class);
只需要像调用其他方法一样调用要测试的方法,并为每个被调用的具体方法提供期望值 - 不确定如何在Mockito中实现,但我相信使用EasyMock是可能的。
所有这些都只是创建一个YouClass
的具体实例,并为您提供提供每个抽象方法的空实现所需的努力。
另外,我经常发现在我的测试中实现抽象类非常有用,因为它可以作为一个示例实现通过其公共接口进行测试,尽管这取决于抽象类提供的功能。
mock(MyAbstractClass.class, withSettings().useConstructor(arg1, arg2).defaultAnswer(CALLS_REAL_METHODS))
来模拟需要构造函数参数的抽象类。 - Gediminas Rimsa