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());
}
}
The following suggestion lets you test abstract classes without creating a "real" subclass - the Mock is the subclass and only a partial mock.
Use Mockito.mock(My.class, Answers.CALLS_REAL_METHODS), then mock any abstract methods that are invoked.
Example:
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());
}
}
Note: The beauty of this solution is that you do not have to implement the abstract methods. CALLS_REAL_METHODS causes all real methods to be run as is, as long as you don't stub them in your test.
In my honest opinion, this is neater than using a spy, since a spy requires an instance, which means you have to create an instantiable subclass of your abstract class.
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();
}
}
If you just need to test some of the concrete methods without touching any of the abstracts, you can use CALLS_REAL_METHODS (see Morten's answer), but if the concrete method under test calls some of the abstracts, or unimplemented interface methods, this won't work -- Mockito will complain "Cannot call real method on java interface."
(Yes, it's a lousy design, but some frameworks, e.g. Tapestry 4, kind of force it on you.)
The workaround is to reverse this approach -- use the ordinary mock behavior (i.e., everything's mocked/stubbed) and use doCallRealMethod() to explicitly call out the concrete method under test. 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();
}
}
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()));
You can achieve this by using a spy (use the latest version of Mockito 1.8+ though).
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 的抽象方法(换句话说,测试用例将使用 模板方法设计模式)。
Mocking frameworks are designed to make it easier to mock out dependencies of the class you are testing. When you use a mocking framework to mock a class, most frameworks dynamically create a subclass, and replace the method implementation with code for detecting when a method is called and returning a fake value.
When testing an abstract class, you want to execute the non-abstract methods of the Subject Under Test (SUT), so a mocking framework isn't what you want.
Part of the confusion is that the answer to the question you linked to said to hand-craft a mock that extends from your abstract class. I wouldn't call such a class a mock. A mock is a class that is used as a replacement for a dependency, is programmed with expectations, and can be queried to see if those expectations are met.
Instead, I suggest defining a non-abstract subclass of your abstract class in your test. If that results in too much code, than that may be a sign that your class is difficult to extend.
An alternative solution would be to make your test case itself abstract, with an abstract method for creating the SUT (in other words, the test case would use the Template Method design pattern).
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();
}
}
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();
}
}
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();
// ...
}
}
缺点是如果需要构造函数参数,则无法使用它。
Mockito allows mocking abstract classes by means of the @Mock annotation:
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();
// ...
}
}
The disadvantage is that it cannot be used if you need constructor parameters.
What really makes me feel bad about mocking abstract classes is the fact, that neither the default constructor YourAbstractClass() gets called (missing super() in mock) nor seems there to be any way in Mockito to default initialize mock properties (e.g List properties with empty ArrayList or LinkedList).
My abstract class (basically the class source code gets generated) does NOT provide a dependency setter injection for list elements, nor a constructor where it initializes the list elements (which I tried to add manually).
Only the class attributes use default initialization:
private List<MyGenType> dep1 = new ArrayList<MyGenType>();
private List<MyGenType> dep2 = new ArrayList<MyGenType>();
So there is NO way to mock an abstract class without using a real object implementation (e.g inner class definition in unit test class, overriding abstract methods) and spying the real object (which does proper field initialization).
Too bad that only PowerMock would help here further.
private AbstractClassName classToTest;
@Before
public void preTestSetup()
{
classToTest = new AbstractClassName() { };
}
// Test the AbstractClassName methods.
You can extend the abstract class with an anonymous class in your test.
For example (using Junit 4):
private AbstractClassName classToTest;
@Before
public void preTestSetup()
{
classToTest = new AbstractClassName() { };
}
// Test the AbstractClassName methods.
Assuming your test classes are in the same package (under a different source root) as your classes under test you can simply create the mock:
YourClass yourObject = mock(YourClass.class);
and call the methods you want to test just as you would any other method.
You need to provide expectations for each method that is called with the expectation on any concrete methods calling the super method - not sure how you'd do that with Mockito, but I believe it's possible with EasyMock.
All this is doing is creating a concrete instance of YouClass and saving you the effort of providing empty implementations of each abstract method.
As an aside, I often find it useful to implement the abstract class in my test, where it serves as an example implementation that I test via its public interface, although this does depend on the functionality provided by the abstract class.
发布评论
评论(12)
您可以实例化一个匿名类,注入您的模拟,然后测试该类。
请记住,抽象类
ClassUnderTest
的属性myDependencyService
的可见性必须受到保护
。You can instantiate an anonymous class, inject your mocks and then test that class.
Keep in mind that the visibility must be
protected
for the propertymyDependencyService
of the abstract classClassUnderTest
.PowerMock 的
Whitebox.invokeMethod(..)
在这种情况下会很方便。PowerMock's
Whitebox.invokeMethod(..)
can be handy in this case.以下建议允许您测试抽象类,而无需创建“真正的”子类 - 模拟是子类,并且只是部分模拟。
使用
Mockito.mock(My.class, Answers.CALLS_REAL_METHODS)
,然后模拟调用的任何抽象方法。示例:
注意:此解决方案的优点在于您不必实现抽象方法。
CALLS_REAL_METHODS
会导致所有实际方法按原样运行,只要您不在测试中对它们进行存根即可。在我看来,这比使用间谍更简洁,因为间谍需要一个实例,这意味着您必须创建抽象类的可实例化子类。
The following suggestion lets you test abstract classes without creating a "real" subclass - the Mock is the subclass and only a partial mock.
Use
Mockito.mock(My.class, Answers.CALLS_REAL_METHODS)
, then mock any abstract methods that are invoked.Example:
Note: The beauty of this solution is that you do not have to implement the abstract methods.
CALLS_REAL_METHODS
causes all real methods to be run as is, as long as you don't stub them in your test.In my honest opinion, this is neater than using a spy, since a spy requires an instance, which means you have to create an instantiable subclass of your abstract class.
如果您只需要测试一些具体方法而不涉及任何抽象,则可以使用 CALLS_REAL_METHODS (请参阅Morten 的回答),但是如果被测试的具体方法调用一些抽象方法或未实现的接口方法,则这将不起作用 - Mockito 会抱怨“无法在 java 接口上调用真正的方法。”
(是的,这是一个糟糕的设计,但是有些框架,例如 Tapestry 4,有点强迫你这样做。)
解决方法是扭转这种方法——使用普通的模拟行为(即,所有东西都被模拟/存根)并使用
doCallRealMethod()
显式调用被测试的具体方法。 例如更新为添加:
对于非 void 方法,您需要使用
thenCallRealMethod()
相反,例如:否则 Mockito 会抱怨“检测到未完成的存根”。
If you just need to test some of the concrete methods without touching any of the abstracts, you can use
CALLS_REAL_METHODS
(see Morten's answer), but if the concrete method under test calls some of the abstracts, or unimplemented interface methods, this won't work -- Mockito will complain "Cannot call real method on java interface."(Yes, it's a lousy design, but some frameworks, e.g. Tapestry 4, kind of force it on you.)
The workaround is to reverse this approach -- use the ordinary mock behavior (i.e., everything's mocked/stubbed) and use
doCallRealMethod()
to explicitly call out the concrete method under test. E.g.Updated to add:
For non-void methods, you'll need to use
thenCallRealMethod()
instead, e.g.:Otherwise Mockito will complain "Unfinished stubbing detected."
您可以通过使用间谍来实现这一点(不过请使用最新版本的 Mockito 1.8+)。
You can achieve this by using a spy (use the latest version of Mockito 1.8+ though).
模拟框架旨在更轻松地模拟您正在测试的类的依赖关系。 当您使用模拟框架来模拟类时,大多数框架会动态创建子类,并将方法实现替换为用于检测何时调用方法并返回假值的代码。
在测试抽象类时,您希望执行被测主题 (SUT) 的非抽象方法,因此模拟框架不是您想要的。
部分困惑在于,您所链接的问题的答案是手工制作一个从您的抽象类扩展的模拟。 我不会称这样的课程为模拟课程。 模拟是一个类,用作依赖项的替代品,根据期望进行编程,并且可以查询以查看是否满足这些期望。
相反,我建议在测试中定义抽象类的非抽象子类。 如果这会导致代码过多,则可能表明您的类难以扩展。
另一种解决方案是使测试用例本身抽象,并使用创建 SUT 的抽象方法(换句话说,测试用例将使用 模板方法设计模式)。
Mocking frameworks are designed to make it easier to mock out dependencies of the class you are testing. When you use a mocking framework to mock a class, most frameworks dynamically create a subclass, and replace the method implementation with code for detecting when a method is called and returning a fake value.
When testing an abstract class, you want to execute the non-abstract methods of the Subject Under Test (SUT), so a mocking framework isn't what you want.
Part of the confusion is that the answer to the question you linked to said to hand-craft a mock that extends from your abstract class. I wouldn't call such a class a mock. A mock is a class that is used as a replacement for a dependency, is programmed with expectations, and can be queried to see if those expectations are met.
Instead, I suggest defining a non-abstract subclass of your abstract class in your test. If that results in too much code, than that may be a sign that your class is difficult to extend.
An alternative solution would be to make your test case itself abstract, with an abstract method for creating the SUT (in other words, the test case would use the Template Method design pattern).
尝试使用自定义答案。
例如:
对于抽象方法,它将返回模拟,对于具体方法,它将调用真实方法。
Try using a custom answer.
For example:
It will return the mock for abstract methods and will call the real method for concrete methods.
Mockito 允许通过 @Mock 注解来模拟抽象类:
缺点是如果需要构造函数参数,则无法使用它。
Mockito allows mocking abstract classes by means of the
@Mock
annotation:The disadvantage is that it cannot be used if you need constructor parameters.
真正让我对模拟抽象类感到难过的是,默认构造函数
YourAbstractClass()
都没有被调用(模拟中缺少super()
),似乎也没有调用。可以在 Mockito 中以任何方式默认初始化模拟属性(例如带有空ArrayList
或LinkedList
的List
属性)。我的抽象类(基本上是生成类源代码)不提供列表元素的依赖项设置器注入,也不提供初始化列表元素的构造函数(我尝试手动添加)。
只有类属性使用默认初始化:
因此,如果不使用真实对象实现(例如单元测试类中的内部类定义、重写抽象方法)和监视真实对象(执行正确的字段初始化),就无法模拟抽象类。
太糟糕了,只有 PowerMock 可以进一步提供帮助。
What really makes me feel bad about mocking abstract classes is the fact, that neither the default constructor
YourAbstractClass()
gets called (missingsuper()
in mock) nor seems there to be any way in Mockito to default initialize mock properties (e.gList
properties with emptyArrayList
orLinkedList
).My abstract class (basically the class source code gets generated) does NOT provide a dependency setter injection for list elements, nor a constructor where it initializes the list elements (which I tried to add manually).
Only the class attributes use default initialization:
So there is NO way to mock an abstract class without using a real object implementation (e.g inner class definition in unit test class, overriding abstract methods) and spying the real object (which does proper field initialization).
Too bad that only PowerMock would help here further.
您可以在测试中使用匿名类扩展抽象类。
例如(使用 Junit 4):
You can extend the abstract class with an anonymous class in your test.
For example (using Junit 4):
假设您的测试类与被测试的类位于同一包中(在不同的源根目录下),您可以简单地创建模拟:
并像调用任何其他方法一样调用您想要测试的方法。
您需要为调用的每个方法提供期望,并为调用超级方法的任何具体方法提供期望 - 不确定如何使用 Mockito 做到这一点,但我相信 EasyMock 是可能的。
所有这一切都是为了创建
YouClass
的具体实例,并节省您提供每个抽象方法的空实现的精力。顺便说一句,我经常发现在测试中实现抽象类很有用,它作为我通过其公共接口测试的示例实现,尽管这确实取决于抽象类提供的功能。
Assuming your test classes are in the same package (under a different source root) as your classes under test you can simply create the mock:
and call the methods you want to test just as you would any other method.
You need to provide expectations for each method that is called with the expectation on any concrete methods calling the super method - not sure how you'd do that with Mockito, but I believe it's possible with EasyMock.
All this is doing is creating a concrete instance of
YouClass
and saving you the effort of providing empty implementations of each abstract method.As an aside, I often find it useful to implement the abstract class in my test, where it serves as an example implementation that I test via its public interface, although this does depend on the functionality provided by the abstract class.