Skip to content

Mockito: @InjectMocks, @Mock, @Spy Annotations in Depth

The Wiring Harness Analogy

When a car rolls off an assembly line, a wiring harness connects all electrical components — sensors, actuators, lights, engine control unit — without you manually plugging every wire yourself. The harness knows what plugs where based on the shape of the connector. @InjectMocks works the same way: it knows which mock objects to connect to which fields in your class under test, based on types and naming conventions.

Understanding the three ways this harness can wire things together — constructor injection, setter injection, and field injection — and the exact rules that govern each, prevents nearly every bug people encounter with @InjectMocks.


Three Ways to Create Mocks

There are three broad strategies for creating mock objects in Mockito.

1. Manual Creation

You call Mockito.mock() and Mockito.spy() yourself:

java
class OrderServiceTest {

    PaymentGateway paymentGateway;
    OrderRepository orderRepository;
    OrderService orderService;

    @BeforeEach
    void setUp() {
        // Manual creation — safe because @BeforeEach runs before every test
        paymentGateway = Mockito.mock(PaymentGateway.class);
        orderRepository = Mockito.mock(OrderRepository.class);
        orderService = new OrderService(paymentGateway, orderRepository);
    }
}

Safe pattern: placing mock creation in @BeforeEach guarantees a fresh mock for each test.

Unsafe pattern: creating mocks at field initializer level is fine only if the test lifecycle is PER_METHOD (the default). With PER_CLASS, the same mock object is shared across all tests and stub leakage occurs.

2. Annotations Without Extension

You add annotations but manage the lifecycle manually via MockitoAnnotations.openMocks:

java
class OrderServiceTest {

    @Mock
    PaymentGateway paymentGateway;

    @Mock
    OrderRepository orderRepository;

    @InjectMocks
    OrderService orderService;

    private AutoCloseable closeable;

    @BeforeEach
    void setUp() {
        // Opens mock processing — must be called explicitly
        closeable = MockitoAnnotations.openMocks(this);
    }

    @AfterEach
    void tearDown() throws Exception {
        // Must close explicitly to release thread-local state
        closeable.close();
    }
}

openMocks(this) performs four internal steps:

  1. Reflection scan: finds all fields annotated with @Mock, @Spy, @InjectMocks.
  2. Mock/spy creation: calls Mockito.mock() or Mockito.spy() for each @Mock or @Spy field.
  3. Injection: creates a real instance for the @InjectMocks field and injects the mocks via constructor, setter, or field.
  4. thread local storage: records which fields are mocked for this thread so Mockito can verify interactions.

The return value is AutoCloseable. You must close it in @AfterEach. Mockito stores injection metadata in thread local memory. If you use a thread pool (common in CI environments), threads are reused between tests. Without closing, the previous test's metadata remains on the thread and produces unpredictable verification failures.

Disadvantages of this approach:

  • Boilerplate: every test class needs the openMocks / close pair.
  • Easy to forget close() — leads to memory pressure and phantom failures.
  • Easy to misplace openMocks in @BeforeAll instead of @BeforeEach — causing shared mock state.
java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    PaymentGateway paymentGateway;

    @Mock
    OrderRepository orderRepository;

    @InjectMocks
    OrderService orderService;

    @Test
    void placeOrder_success() {
        when(paymentGateway.charge(100.0)).thenReturn(true);
        assertTrue(orderService.placeOrder(100.0));
    }
}

MockitoExtension comes from the mockito-junit-jupiter library (which also bundles mockito-core, so you only need one dependency):

xml
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>5.x.x</version>
    <scope>test</scope>
</dependency>

MockitoExtension implements BeforeEachCallback and AfterEachCallback:

  • Before each test: calls openMocks(testInstance) — creates mocks, injects them.
  • After each test: closes the session — releases thread local metadata.

You write zero lifecycle code. The extension handles everything.


How @InjectMocks Chooses an Injection Strategy

@InjectMocks tries three strategies in order. Understanding the exact rules for each strategy is where most confusion lives.

Strategy 1: Constructor Injection (Highest Priority)

Rules:

  1. Pick the constructor with the most parameters.
  2. For object type parameters: use a matching mock if one exists, otherwise use null.
  3. For primitive type parameters: do not guess a value. If a primitive cannot be resolved, the entire constructor attempt fails.
  4. If the chosen constructor cannot be fully satisfied (primitive with no mock, or explicitly unresolvable), fall back to the default no argument constructor.

Use case: exact match, all mocks present

java
// Class under test
public class OrderService {
    private final PaymentGateway gateway;
    private final OrderRepository repo;

    public OrderService(PaymentGateway gateway, OrderRepository repo) {
        this.gateway = gateway;
        this.repo = repo;
    }
}

// Test: both mocks present, constructor is fully satisfied
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
    @Mock PaymentGateway gateway;
    @Mock OrderRepository repo;
    @InjectMocks OrderService orderService; // gateway and repo injected via constructor
}

Use case: most parameter constructor picked, one dependency is null

java
// Class with three dependencies and two constructors
public class OrderService {
    private PaymentGateway gateway;
    private OrderRepository repo;
    private SalesRepository salesRepo; // no mock provided in test

    // Two-param constructor
    public OrderService(PaymentGateway g, OrderRepository r) { ... }

    // Three-param constructor — will be picked (most params)
    public OrderService(PaymentGateway g, OrderRepository r, SalesRepository s) {
        this.gateway = g;
        this.repo = r;
        this.salesRepo = s; // will be null — no @Mock for SalesRepository
    }
}

@InjectMocks picks the three-param constructor. PaymentGateway and OrderRepository mocks are present, so they are injected. SalesRepository has no mock, so null is used. Constructor injection succeeds. salesRepo will be null.

Use case: primitive parameter causes fallback

java
public class OrderService {
    public OrderService(PaymentGateway g, OrderRepository r, int maxRetries) { ... }
    public OrderService() { } // default constructor
}

The three-param constructor is picked. g and r have mocks. maxRetries is a primitive — Mockito will not guess 0 or any other value. Constructor injection fails. Mockito falls back to the no argument constructor. Since the no argument constructor exists, the object is created with no dependencies injected via constructor. Mockito then attempts setter or field injection for the remaining fields.

If no no argument constructor exists either, @InjectMocks fails completely and throws an exception.

Strategy 2: Setter Injection (Second Priority)

Setter injection runs when object creation fell through to the default no argument constructor. It requires:

  • A no argument constructor (for object creation).
  • Setter methods that follow the JavaBean naming convention: setFieldName(Type value).
  • Each setter must accept exactly one argument.
  • Setter injection does not work for final fields.
java
public class OrderService {
    private PaymentGateway gateway;
    private OrderRepository repo;

    public OrderService() { }

    // Setter must be named setGateway — not initGateway, not gateway
    public void setGateway(PaymentGateway gateway) {
        this.gateway = gateway;
    }

    // No setter for repo
}

For fields with no setter, Mockito falls through to field injection.

Strategy 3: Field Injection (Lowest Priority)

Mockito uses reflection to set fields directly, even if they are private. It does not work for final fields.

java
public class OrderService {
    // Even private fields are set via reflection
    private PaymentGateway gateway;
    private OrderRepository repo;
    // No constructor, no setters — Mockito injects directly
}

This is the most permissive strategy but also the most fragile — your production class has no constructor or setter, so its design is not injectable without reflection.


Complete Injection Decision Flow

@InjectMocks processes subject class


Step 1: CONSTRUCTOR INJECTION
Pick constructor with most parameters

        ├─ All params resolved? ──Yes──▶ Object created. DONE.

        ├─ Object param missing mock? ──▶ Use null. Continue.

        └─ Primitive param present? ──No mock exists──▶ FAIL


        Fall back to default no-arg constructor

                ├─ Found? ──Yes──▶ Object created via no-arg
                │                        │
                │                        ▼
                │               Step 2: SETTER INJECTION
                │               Look for setXxx(Type) methods
                │               Inject matching mocks
                │                        │
                │                        ▼
                │               Step 3: FIELD INJECTION
                │               For fields with no setter:
                │               Inject via reflection (non-final only)

                └─ Not found? ──▶ @InjectMocks fails with exception

Comprehensive Example

java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private PaymentGateway paymentGateway;

    @Mock
    private OrderRepository orderRepository;

    // @Spy wraps a real object — real methods run unless stubbed
    @Spy
    private List<String> auditLog = new ArrayList<>();

    @InjectMocks
    private OrderService orderService;

    @Test
    void placeOrder_shouldChargeAndSave_whenAmountPositive() {
        // Arrange
        when(paymentGateway.charge(150.0)).thenReturn(true);
        doNothing().when(orderRepository).save(any());

        // Act
        boolean result = orderService.placeOrder(150.0);

        // Assert
        assertTrue(result);
        verify(paymentGateway, times(1)).charge(150.0);
        verify(orderRepository, times(1)).save(any());
    }

    @Test
    void placeOrder_shouldReturnFalse_whenPaymentFails() {
        when(paymentGateway.charge(anyDouble())).thenReturn(false);

        boolean result = orderService.placeOrder(50.0);

        assertFalse(result);
        verifyNoInteractions(orderRepository); // repository should not be called on failure
    }
}

Thread Local and Memory Safety

openMocks stores injection metadata in thread local memory. Without close() (or the extension handling teardown), that memory accumulates across tests. In environments that reuse threads (thread pools, parallel test execution), stale metadata from a previous test class can interfere with a new test class running on the same thread.

MockitoExtension handles this automatically. If you use MockitoAnnotations.openMocks manually, always capture the AutoCloseable and close it in @AfterEach:

java
// Always close — do not forget
@AfterEach
void tearDown() throws Exception {
    closeable.close();
}

Interview Questions & Pitfalls

Q1: What are the three injection strategies used by @InjectMocks, and what is their priority?

Constructor injection is tried first (highest priority). If the selected constructor cannot be satisfied — particularly if a primitive parameter has no matching mock — Mockito falls back to the default no argument constructor. Once an object is created via the no argument constructor, setter injection is attempted using JavaBean naming convention setters. Finally, for any fields that still lack values, Mockito uses reflection based field injection. Priority: constructor → setter → field.

Q2: What happens when @InjectMocks encounters a primitive constructor parameter and no matching mock?

Mockito will not attempt to guess a default value for a primitive. The constructor attempt fails. Mockito then looks for a default no argument constructor. If found, it creates the object with that constructor and proceeds with setter and field injection. If no no argument constructor exists, @InjectMocks throws an exception.

Q3: Why must MockitoAnnotations.openMocks be called in @BeforeEach and not @BeforeAll?

@BeforeAll runs once per test class. With openMocks in @BeforeAll, all test methods share the same mock objects. Stubbing and invocation counts from one test leak into subsequent tests, producing test order dependency and intermittent failures. @BeforeEach guarantees a fresh set of mocks for every test.

Q4: Does setter injection work for final fields?

No. Setter methods cannot update final fields in Java — the value is set at construction time and cannot change. Field injection via reflection also cannot override final fields. The only way to populate a final field with a mock is through constructor injection, where the mock is passed as a constructor argument.

Q5: What is the difference between @Mock and @Spy when used with @InjectMocks?

@Mock creates a pure fake object: all methods return defaults unless stubbed; no real code ever runs. @Spy wraps a real instance: methods that are not stubbed execute the actual implementation. Both @Mock and @Spy objects are eligible for injection into the @InjectMocks subject. The choice depends on whether you want the dependency to behave as a complete fake or as a real object with selective overrides.

Q6: What is the risk of using field-level mock initializers (private Foo foo = mock(Foo.class)) with PER_CLASS lifecycle?

With @TestInstance(Lifecycle.PER_CLASS), the test class is instantiated once. Field initializers also run once. Every test method receives the same mock object. Since stub registrations, verification counts, and interaction history accumulate across tests, later tests are affected by earlier tests' activity. The safest approach under PER_CLASS is to reset or re-create mocks in a @BeforeEach method.