Search code examples
javaunit-testingmockitopowermock

Mocking an abstract class and injecting classes with Mockito annotations?


Is it possible to both mock an abstract class and inject it with mocked classes using Mockito annotations. I now have the following situation:

@Mock private MockClassA mockClassA;
@Mock private MockClassB mockClassB;

@Mock(answer = Answers.CALLS_REAL_METHODS) private AbstractClassUnderTest abstractClassUnderTest;

@Before
public void init() {
    MockitoAnnotations.initMocks(this);
    Whitebox.setInternalState(abstractClassUnderTest, mockClassA);
    Whitebox.setInternalState(abstractClassUnderTest, mockClassB);
}

I'd like to use something like @InjectMocks on AbstractClassUnderTest but it can't be used in combination with @Mock. The current situation, with Whitebox from Powermock, works but I'm curious if it's possible to solve it with just annotations. I couldn't find any solutions or examples.

(I know about the objections to test abstract classes and I'd personally rather test a concrete implementation and just use @InjectMocks.)


Solution

  • I am not aware of any way to go about this, for one clear reason: @InjectMocks is meant for non-mocked systems under test, and @Mock is meant for mocked collaborators, and Mockito is not designed for any class to fill both those roles in the same test.

    Bear in mind that your @Mock(CALLS_REAL_METHODS) declaration is inherently dangerous: You're testing your AbstractClassUnderTest, but you are not running any constructors or initializing any fields. I don't think you can expect a test with this design to be realistic or robust, no matter what annotations can or cannot do for you. (Personally, I was previously in favor of real partial mocks of abstract classes as a "tool in the toolbox", but I'm coming around to thinking they're too far removed from reality to be useful.)

    Were I in your position, I would create a small override implementation for testing:

    @RunWith(JUnit4.class) public class AbstractClassTest {
      /** Minimial AbstractClass implementation for testing. */
      public static class SimpleConcreteClass extends AbstractClass {
        public SimpleConcreteClass() { super("foo", "bar", 42); }
        @Override public void abstractMethod1() {}
        @Override public String abstractMethod2(int parameter) { return ""; }
      }
    
      @InjectMocks SimpleConcreteClass classUnderTest;
      @Mock mockClassA;
      @Mock mockClassB;
    }
    

    At this point, you have a simple and predictable AbstractClass implementation, which you can use even without a mocking framework if you just wanted to test that AbstractClass has the same API for extension that it did before. (This is an often-overlooked test for abstract classes.) You can even extract this, as it may be useful for other testing: Should you want to override the abstract behavior for a single test class, you can create an anonymous inner class with just a single method override, or you can set classUnderTest = spy(classUnderTest); to set up Mockito proxying and the behavior you want.

    (Bear in mind that @InjectMocks and @Spy can't be used reliably together, as documented in this GitHub issue and the Google Code and mailing list threads to which it links.)