4

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

Sparwer
  • 197
  • 2
  • 4
  • 13

2 Answers2

10

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

Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
4

I found some trick with mocking field before initialization.

@InjectMocks
private AbstractClass abstractClass;
@Mock 
private MockClass mockClass;

@Before
public void init() {
    abstractClass= mock(AbstractClass.class, Answers.CALLS_REAL_METHODS);
    MockitoAnnotations.initMocks(this);
}

Maybe it'll help someone.

  • 1
    Note: in order for it to work, make sure it is *NOT* marked by `@RunWith(MockitoJUnitRunner.class)` – Andrey Lebedenko Nov 14 '19 at 10:35
  • If you need have the `@RunWith(MockitoJUnitRunner.class)`, then remove the @InjectMocks annotation, still having the @Before. Like below ```private AbstractClass abstractClass; @Before public void init() { abstractClass= mock(AbstractClass.class, Answers.CALLS_REAL_METHODS); }``` – Jeeka Dec 21 '22 at 08:31