Using Mockito to test abstract classes
Asked Answered
F

12

259

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?

Foveola answered 6/7, 2009 at 14:40 Comment(2)
As of Mockito 1.10.12, Mockito supports spying/mocking abstract classes directly: SomeAbstract spy = spy(SomeAbstract.class);Starter
As of Mockito 2.7.14, you can also mock abstract classess that require constructor arguments via mock(MyAbstractClass.class, withSettings().useConstructor(arg1, arg2).defaultAnswer(CALLS_REAL_METHODS))Brainless
I
376

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.

Isogonic answered 30/11, 2010 at 19:25 Comment(8)
+1 for helping me solve my issue that was unrelated to the question. The CALLS_REAL_METHODS was useful for injecting a stub object into my SUT. A stub made more sense than a mock since the return values were a bit complex.Volgograd
As noted below, this doesn't work when the abstract class calls abstract methods in order to be tested, which is often the case.Zelma
This actually does work when the abstract class calls abstract methods. Just use the doReturn or doNothing syntax instead of Mockito.when for stubbing the abstract methods, and if you stub any concrete calls, make sure stubbing the abstract calls comes first.Extrinsic
How can I inject dependencies in this kind of object (mocked abstract class calling real methods)?Excruciating
This behaves in unexpected ways if the class in question has instance initializers. Mockito skips initializers for mocks, which means instance variables that are initialized inline will be unexpectedly null, which can cause NPEs.Safekeeping
What is Mockito.CALLS_REAL_METHODS?Yacov
What if the abstract class constructor takes one or more parameters?Experience
@S.D. If the abstract class constructor takes parameters, you do this: https://mcmap.net/q/16508/-testing-abstract-classes-with-arguments-on-constructorOdelia
S
77

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."

Sleepless answered 12/9, 2011 at 23:14 Comment(3)
This will work in some cases, however Mockito does not call the constructor of the underlying abstract class with this method. This may cause the "real method" to fail due to an unexpected scenario being created. Thus, this method will not work in all cases either.Zelma
Yep, you can't count on the state of the object at all, only the code in the method being called.Sleepless
Oh so the object methods get separated from the state, great.Desta
Z
20

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()));
Zelma answered 28/7, 2009 at 4:14 Comment(0)
P
16

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).

Phocine answered 6/7, 2009 at 15:6 Comment(0)
R
8

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.

Rosendorosene answered 15/7, 2009 at 20:38 Comment(0)
D
6
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();
  }
}
Doubles answered 21/10, 2020 at 15:30 Comment(1)
This is exactly what I needed - a test for an abstract class with @InjectMocks. Thanks for adding this answer!Coeducation
I
5

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.

Inspect answered 2/11, 2010 at 18:30 Comment(0)
A
5

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.

Adonis answered 15/8, 2018 at 22:19 Comment(0)
V
4

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.
Vines answered 13/3, 2015 at 19:43 Comment(0)
S
2

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.

Soho answered 6/7, 2009 at 14:53 Comment(4)
But using the mock will not test the concrete methods of YourClass, or am I wrong? This is not what I seek.Foveola
That's correct, the above won't work if you want to invoke the concrete methods on the abstract class.Zelma
Apologies, I'll edit the bit about the expectation, which are required for each method you call not just the abstract ones.Soho
but then you are still testing your mock, not the concrete methods.Warrington
E
1

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.

Excruciating answered 27/7, 2017 at 11:27 Comment(0)
M
0

PowerMock's Whitebox.invokeMethod(..) can be handy in this case.

Mansour answered 27/7, 2018 at 13:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.