230

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?

Community
  • 1
  • 1
ripper234
  • 202,011
  • 255
  • 600
  • 878
  • 3
    As of Mockito [1.10.12](http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#30), Mockito supports spying/mocking abstract classes directly: `SomeAbstract spy = spy(SomeAbstract.class);` – pesche Dec 30 '14 at 14:21
  • 9
    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))` – Gediminas Rimsa Jul 13 '18 at 07:46

12 Answers12

344

The following suggestion let's you test abstract classes without creating a "real" subclass - the Mock is the subclass.

use Mockito.mock(My.class, Mockito.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, Mockito.CALLS_REAL_METHODS);
        Assert.assertSomething(my.methodUnderTest());
    }
}

Note: The beauty of this solution is that you do not have to implement the abstract methods, as long as they are never invoked.

In my honest opinion, this is neater than using a spy, since a spy requires an instance, which means you have to create an instantiatable subclass of your abstract class.

L1ghtk3ira
  • 2,330
  • 3
  • 26
  • 58
  • +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. – Snekse Dec 07 '11 at 22:59
  • 17
    As noted below, this doesn't work when the abstract class calls abstract methods in order to be tested, which is often the case. – Richard Nichols Aug 06 '12 at 03:34
  • 12
    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. – Gonen I Sep 06 '14 at 18:07
  • 2
    How can I inject dependencies in this kind of object (mocked abstract class calling real methods)? – Samuel Jul 27 '17 at 11:11
  • 2
    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. – digitalbath Sep 07 '17 at 18:57
  • What is **Mockito.CALLS_REAL_METHODS**? – IgorGanapolsky Aug 03 '18 at 13:29
  • 2
    What if the abstract class constructor takes one or more parameters? – S.D. Mar 14 '19 at 08:22
70

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

Community
  • 1
  • 1
David Moles
  • 39,436
  • 24
  • 121
  • 210
  • 9
    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. – Richard Nichols Aug 06 '12 at 03:38
  • 3
    Yep, you can't count on the state of the object at all, only the code in the method being called. – David Moles Nov 12 '12 at 23:21
  • Oh so the object methods get separated from the state, great. – haelix Nov 25 '18 at 19:58
17

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()));
Richard Nichols
  • 1,870
  • 17
  • 19
14

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

NamshubWriter
  • 22,197
  • 2
  • 38
  • 54
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.

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.

Thomas Heiss
  • 51
  • 1
  • 1
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.

Nick Holt
  • 31,429
  • 4
  • 46
  • 56
2

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.
DwB
  • 33,855
  • 10
  • 50
  • 78
2

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.

Jorge Pastor
  • 660
  • 4
  • 10
0

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.

Samuel
  • 2,120
  • 1
  • 19
  • 31
0

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

Noumenon
  • 3,184
  • 4
  • 39
  • 57
Smart Coder
  • 809
  • 8
  • 13
0
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();
  }
}
Lokesh Sharma
  • 145
  • 1
  • 5