I'd like to test an abstract class. Sure, I can manually write a mock that inherits from the class.
Can I do this using a mocking framework (I'm using Mockito) instead of hand-crafting my mock? How?
I'd like to test an abstract class. Sure, I can manually write a mock that inherits from the class.
Can I do this using a mocking framework (I'm using Mockito) instead of hand-crafting my mock? How?
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.
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();
}
}
Updated to add:
For non-void methods, you'll need to use thenCallRealMethod()
instead, e.g.:
when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();
Otherwise Mockito will complain "Unfinished stubbing detected."
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()));
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:
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);
}
}
It will return the mock for abstract methods and will call the real method for concrete methods.
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();
}
}
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.
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.
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.
You can instantiate an anonymous class, inject your mocks and then test that class.
@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);
}
}
Keep in mind that the visibility must be protected
for the property myDependencyService
of the abstract class ClassUnderTest
.
PowerMock's Whitebox.invokeMethod(..)
can be handy in this case.
© 2022 - 2024 — McMap. All rights reserved.
SomeAbstract spy = spy(SomeAbstract.class);
– Startermock(MyAbstractClass.class, withSettings().useConstructor(arg1, arg2).defaultAnswer(CALLS_REAL_METHODS))
– Brainless