使用Mockitotesting抽象类
我想testing一个抽象类。 当然,我可以手动编写一个从类inheritance的模拟 。
我可以使用模拟框架(我正在使用Mockito)而不是手工制作模拟器吗? 怎么样?
下面的build议让我们testing抽象类而不创build一个“真正的”子类 – Mock 是子类。
使用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()); } }
注意:这个解决scheme的好处是,你不必实现抽象方法,只要它们从不被调用。
在我看来,这比使用间谍更简单,因为间谍需要一个实例,这意味着你必须创build一个抽象类的实例化子类。
如果你只需要testing一些具体的方法而不触及任何摘要,你可以使用CALLS_REAL_METHODS
(见Morten的答案 ),但是如果被testing的具体方法调用某些摘要或未实现的接口方法,工作 – Mockito会抱怨“不能调用Java接口上真正的方法”。
(是的,这是一个糟糕的devise,但一些框架,如挂毯4,强迫你。)
解决方法是颠倒这种方法 – 使用普通的模拟行为(即所有的模拟/存根),并使用doCallRealMethod()
明确地调用被testing的具体方法。 例如
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会抱怨“检测到未完成的残片”。
你可以通过使用间谍(尽pipe使用最新版本的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()));
模拟框架的devise是为了更容易地模拟出你正在testing的类的依赖关系。 当你使用一个模拟框架来模拟一个类,大多数框架dynamic地创build一个子类,并用代码replace方法实现来检测何时调用一个方法并返回一个假值。
当testing一个抽象类时,你想要执行被testing主题(SUT)的非抽象方法,所以一个嘲笑框架不是你想要的。
混淆的一部分是,你所提到的问题的答案是手工制作一个从你的抽象类延伸出来的模拟。 我不会嘲笑这样的class级。 模拟是一个用来替代依赖关系的类,可以根据期望进行编程,可以查询是否满足这些期望。
相反,我build议在testing中定义抽象类的非抽象子类。 如果这导致了太多的代码,那可能是你的class级难以扩展的标志。
另一种解决scheme是将testing用例本身抽象化,用抽象方法创buildSUT(换句话说,testing用例将使用Template Methoddevise模式)。
尝试使用自定义的答案。
例如:
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()被调用(在模拟中缺lesssuper()),在Mockito中也没有任何方法来默认初始化模拟属性(例如List属性与空的ArrayList或LinkedList)。
我的抽象类(基本上是生成的类源代码)没有为列表元素提供依赖性setter注入,也没有为列表元素初始化(我试图手动添加)的构造函数。
只有类属性使用默认的初始化:private List dep1 = new ArrayList; 私人列表dep2 =新的ArrayList
因此,如果不使用真实的对象实现(例如,unit testing类中的内部类定义,重写抽象方法)和窥探实际对象(做适当的字段初始化),就没有办法模拟抽象类。
太糟糕了,只有PowerMock会在这里进一步帮助。
假设你的testing类与你的类在同一个包中(在不同的源代码下),你可以简单地创build一个模拟:
YourClass yourObject = mock(YourClass.class);
并像您使用其他方法一样调用您想要testing的方法。
你需要提供每种方法的期望值,这些方法是调用超级方法的任何具体方法的期望 – 不知道你如何使用Mockito来做这件事,但我相信EasyMock是可能的。
所有这一切正在创build一个YouClass
的具体实例,并节省您提供每个抽象方法的空实现的努力。
顺便说一句,我经常发现在我的testing中实现抽象类是有用的,在那里它作为我通过它的公共接口testing的一个示例实现,虽然这取决于抽象类提供的function。
您可以在testing中使用匿名类来扩展抽象类。 例如(使用Junit 4):
private AbstractClassName classToTest; @Before public void preTestSetup() { classToTest = new AbstractClassName() { }; } // Test the AbstractClassName methods.
你可以实例化一个匿名类,注入你的模拟,然后testing这个类。
@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
protected
可见性。