1. Overview

In this lesson, we’ll explore how to simplify mock creation and injection using annotation-based support in Mockito. We’ll see how this approach reduces boilerplate and makes our tests more concise and readable. We’ll also look at how to enable this support in a streamlined way using a JUnit extension provided by Mockito.

The relevant module we need to import when starting this lesson is: mock-with-annotations-start.

If we want to reference the fully implemented lesson, we can import: mock-with-annotations-end.

2. Mockito Annotations Overview

Mockito provides annotations that simplify the creation and management of mock objects. Here, we’ll focus on the core annotations used in most tests:

  • @Mock – defines a mock object for a given test class field
  • @InjectMocks – creates an instance  (a real one, not mocked) of the given class test field and injects mock dependencies into it

Mockito also provides other annotations, such as @Spy, @MockitoSettings, and @Captor, which will be covered in separate lessons.

All those annotations are included in the mockito-core dependency, so there’s no need for any other library to use them with an existing project.

The main advantage of using those annotations is to reduce the boilerplate code needed when manually creating mocks, which also makes the test class more readable.

Instead of writing multiple mock() calls and passing them into a constructor, we can define fields at the class level and let Mockito automatically create the associated object.

An important note about annotations is that, in order to work, they must be explicitly initialized. Will cover the available methods to achieve this later in this lesson.

3. Using @Mock

This annotation, when applied to a class-level field, tells Mockito to create a mock object that matches the field’s type.

For instance, if our codebase has a CampaignRepository interface and we want to create a mock instance to support our tests, all we have to do is declare a field with the corresponding type in the test class:

@Mock
private CampaignRepository campaignRepo;

This field must be non-final and non-static so Mockito can manipulate it at runtime.

The @Mock annotation supports optional fields, which we can use to customize the created mock. These are the most useful ones:

  • name – mock name, used in error/warning messages. Defaults to the field name
  • extraInterfaces – a list of interfaces implemented by the created mock instance

For instance, we can use extraInterfaces to instruct Mockito to create a mock that implements Closeable in addition to the mocked class:

@Mock(extraInterfaces = Closeable.class) CampaignRepository campaignRepository;

Now, we can use campaignRepository in our test code as a Closeable whenever needed:

@Test
void givenCampaignRepository_whenInitialized_thenMockImplementsCloseable() {
    assertInstanceOf(Closeable.class, campaignRepository);
}

@Mock‘s other annotation fields, like strictness and mockMaker, are more advanced topics that we won’t be covering here.

4. Using @InjectMocks

The @InjectMocks annotation automatically creates an instance of a test class – not a mock! – and tries to inject any dependencies using the available mocks.

The strategy Mockito uses to perform this injection is as follows:

  • Firstly, if there are constructors with parameters, Mockito will choose the one with the most parameters. Mockito will then try to match the available mock/spy instances with the constructor parameters
  • Next, if there’s only a no-args constructor, Mockito will look for setter methods and try to match them with available mocks.
  • Finally, Mockito will attempt field injection by matching mock or spy instances to fields with compatible types.

Mockito applies these strategies in order and stops as soon as one succeeds, even if it results in partial injection. For example, if a constructor is selected but some dependencies are only satisfied by setters, they will be left uninitialized.

Also, remember that Mockito is not a full dependency injection framework like Spring or CDI. If it cannot find a suitable object to inject, it won’t raise an error. Instead, it will silently inject a null, which may lead to unexpected behavior at runtime.

4.1. Injecting Mocks

Let’s see how @InjectMocks works with a simple example. We know our DefaultCampaignService depends on CampaignRepository. So, let’s create a new DefaultCampaignServiceTest class and add:

@Mock
private CampaignRepository campaignRepo;

@InjectMocks
private DefaultCampaignService service;

Mockito scans DefaultCampaignService and sees it has a constructor that takes a CampaignRepository parameter and injects our @Mock field automatically. This avoids writing repetitive code to instantiate the class under test and makes tests more resilient to code refactoring.

If the service has multiple dependencies, each can be declared with @Mock, and Mockito will inject them all, provided the class under test satisfies @InjectMock’s requirements.

5. Enabling Mockito Annotations

To enable Mockito’s annotation support in our tests, we need to integrate it with JUnit, using one of these techniques:

  • Manually calling Mockito.openMocks() – typically in a @BeforeEach setup method.
  • Using a Mockito JUnit extension, which initializes the annotations automatically on our behalf.

5.1. Using Mockito.openMocks()

This method takes the test class instance as an argument and processes all Mockito-annotated fields, including those inherited from parent classes. We must call this method before running a test; otherwise, the Mockito annotations will have no effect.

Also, it is recommended to save the returned Closeable object in an instance variable and call close() once the test runs in order to release internal resources. Strictly speaking, this is only needed if we need to mock static methods, as this requires some class loader magic.

In those cases, calling close() ensures that Mockito properly restores the class loader state after each test, avoiding side effects that may arise when running all tests in a project.

When using Mockito with JUnit, we’ll typically put this call in a @BeforeEach method and release the mock resources in an @AfterEach method:

class DefaultCampaignServiceTest {

    private AutoCloseable closeable;

    @Mock CampaignRepository campaignRepository;
    @InjectMocks DefaultCampaignService defaultCampaignService;

    @BeforeEach
    void openMocks() {
        closeable = MockitoAnnotations.openMocks(this);
    }

    @AfterEach void closeMocks() throws Exception{
        closeable.close();
    }
    // ... tests
}

As a rule of thumb, we should favor the use of the MockitoExtension, which we’ll introduce in the next section, as it automatically adds those calls.

On the other hand, using openMocks() directly might be necessary in some scenarios, such as when we’re creating or extending a testing framework.

5.2. Using MockitoExtension

The MockitoExtension class is a JUnit 5 extension that automatically initializes any Mockito-annotated field, thus saving us the burden of calling openMocks() directly.

This class is not part of the mockito-core module, so we must add the following dependency to our project:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>${mockito.version}</version>
    <scope>test</scope>
</dependency>

We can find the latest version of this library on Maven Central. Also, make sure to use the same version used for the mockito-core main dependency to avoid runtime errors.

Here is the test class from before, now using MockitoExtension:

@ExtendWith(MockitoExtension.class)
class SomeTest {
    @Mock SomeDependency dependency;
    @InjectMocks SomeService service;

    // tests...
}

Be aware that, although openMocks() and MockitoExtension serve the same purpose, there’s an important difference between them. By default, mocks initialized using MockitoExtension enable strict stubbing, while those created with openMocks() do not.

6. Practical Example from Our Codebase

Below is a test class snippet showing how we might set up DefaultCampaignService with a mocked CampaignRepository:

@ExtendWith(MockitoExtension.class)
class DefaultCampaignServiceAnnotationTest {

    @Mock
    private CampaignRepository campaignRepo;

    @InjectMocks
    private DefaultCampaignService service;

    @Test
    void givenAnnotatedMocks_whenInitialized_thenTheyAreNotNull() {
        assertNotNull(campaignRepo);
        assertNotNull(service);
    }
}

Here, we’re simply confirming that Mockito has created campaignRepo and injected it into the service. To get extra insight into what MockitoExtension did for us, we can set a breakpoint in one of the assertNotNull() calls and run this test using our IDE’s debugger:

Now, if we take a look at the variables view, we can see that the campaingRepo instance variable was initialized with a mock instance, while campaingService has an instance of the actual class. Moreover, we can also see that Mockito injected campaingRepo into the service.

This alone reduces a ton of boilerplate compared to manually creating mocks and passing them into the constructor.

However, it’s important to point out that Mockito is not capable of building complex object graphs using a combination of @Mock and @InjectMocks. For example, the following test will result in a NullPointerException:

class DefaultReportsServiceTest {

    @Mock TaskService taskService;
    @Mock WorkerRepository workerRepository;

    @InjectMocks DefaultWorkerService workerService;
    @InjectMocks DefaultReportsService reportsService;

    @Test
    void givenWorkerIdAndReportBuilder_whenGenerateWorkerReport_shouldFail() {
        var builder = new SimpleMapReportBuilder();

        // ... stubbing omitted

        // Mockito won't inject workerService into the reportsService, so
        // we get an exception here
        assertThrows(NullPointerException.class,() -> reportsService.generateWorkerReport(builder,1L));
    }
}

In this case, Mockito doesn’t know that reportsService depends on workerService, so it won’t inject it automatically. Instead, we need to fall back to manual object creation.

@Test
void givenWorkerIdAndReportBuilder_whenGenerateWorkerReport_returnReport() {
    var builder = new SimpleMapReportBuilder();
    
    // ... mocking handling
    var rs = new DefaultReportsService(taskService,workerService);
    var report = rs.generateWorkerReport(builder,1L);

    // ... assertions and verifications omitted
}

While this might seem limiting, it’s important to note that injecting real objects into the class under test (as we’re doing here) turns the test into an integration test. As a general rule, if we can’t test a class with mocked dependencies, it’s a strong sign that it may need refactoring.

7. Annotated Fields Lifecycle

A question that may arise when dealing with Mockito annotations is about the lifecycle of the created objects. If a test class has multiple test methods, is it safe to reuse them? What if the methods from the class under test have side effects?

Let’s consider this test class:

@ExtendWith(MockitoExtension.class)
class DefaultCampaignServiceAnnotationTest {

    @Mock CampaignRepository campaignRepo;
    @InjectMocks DefaultCampaignService campaignService;

    @Test
    void givenValidId_whenCloseCampaign_thenValidResult() {
        System.out.println("running test: givenValidId_whenCloseCampaign_thenValidResult");
        System.out.println("campaignService.hashCode() = " + campaignService.hashCode());
        System.out.println("campaignRepo.hashCode() = " + campaignRepo.hashCode());
        var result = campaignService.closeCampaign(1L);
        // ... assertions omitted
    }

    @Test
    void givenInvalidId_whenFindById_thenEmptyResult() {
        System.out.println("running test: givenInvalidId_whenFindById_thenEmptyResult");
        System.out.println("campaignService.hashCode() = " + campaignService.hashCode());
        System.out.println("campaignRepo.hashCode() = " + campaignRepo.hashCode());

        var result = campaignService.findById(1L);
        // ... assertions omitted
    }
}

Here, the givenValidId_whenCloseCampaign_thenValidResult() test invokes closeCampaign(), which has a side-effect. Namely, it will modify the given Campaign’s state to closed, together with any associated Task. Are those state changes visible when executing the givenInvalidId_whenFindById_thenEmptyResult() test?

In short, the answer is NO, it won’t.

The reason is that Mockito re-initializes all annotated fields so that each test gets a newly constructed object. To verify this is true, we can print the hashCode() for the created objects inside each test. The console will show a different value on each test method:

running test: givenValidId_whenCloseCampaign_thenValidResult campaignService.hashCode() = 2052489518 campaignRepo.hashCode() = 1997357673 running test: givenInvalidId_whenFindById_thenEmptyResult campaignService.hashCode() = 557725225 campaignRepo.hashCode() = 51460663