Appearance
@Mock as Method / Constructor Parameter (JUnit 5 + MockitoExtension)
The Dependency Injection Analogy
In a real Spring application you often prefer constructor injection because it makes dependencies explicit, allows fields to be final, and is easy to test. A class like this is easy to reason about:
java
public class OrderService {
private final PaymentGateway paymentGateway;
private final OrderRepository orderRepository;
public OrderService(PaymentGateway paymentGateway,
OrderRepository orderRepository) {
this.paymentGateway = paymentGateway;
this.orderRepository = orderRepository;
}
}The same philosophy can be applied to your test classes. Instead of annotating fields with @Mock and letting the extension resolve them before each test, you can declare mocks directly as constructor parameters or method parameters of the test class. JUnit 5's ParameterResolver extension mechanism makes this possible, and MockitoExtension plugs into it seamlessly.
Quick Recap: The Standard Field Approach
Before exploring parameters, here is the baseline everyone knows:
java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private PaymentGateway paymentGateway; // resolved before each test
@Mock
private OrderRepository orderRepository; // resolved before each test
@InjectMocks
private OrderService orderService; // created with mocks injected
@Test
void placeOrder_success() {
when(paymentGateway.charge(100.0)).thenReturn(true);
assertTrue(orderService.placeOrder(100.0));
}
}MockitoExtension implements BeforeEachCallback and AfterEachCallback. Before each test it creates the mocks, creates the @InjectMocks object, and injects everything. After each test it tears down the Mockito session. You do not write any lifecycle code.
How JUnit 5 ParameterResolver Works
JUnit 5 uses a chain of extensions to resolve method and constructor parameters. When JUnit needs to call a test method (or constructor) that has parameters, it asks each registered extension in turn:
- Does your extension support this parameter? (
supportsParameter) - If yes, resolve it. (
resolveParameter)
MockitoExtension implements ParameterResolver. Its supportsParameter returns true for any parameter annotated with @Mock (or @Captor, @Spy). Its resolveParameter creates and returns a fresh Mockito mock of the appropriate type.
This is the foundation for both constructor injection and method injection of mocks.
Using @Mock as a Constructor Parameter
You can declare your test class with a constructor that takes mock objects as parameters:
java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
// Fields are final — cannot be reassigned accidentally
private final PaymentGateway paymentGateway;
private final OrderRepository orderRepository;
private final OrderService orderService;
// Constructor parameters annotated with @Mock
OrderServiceTest(
@Mock PaymentGateway paymentGateway,
@Mock OrderRepository orderRepository) {
this.paymentGateway = paymentGateway;
this.orderRepository = orderRepository;
// You must create the object under test manually here
this.orderService = new OrderService(paymentGateway, orderRepository);
}
@Test
void placeOrder_shouldSucceed_whenPaymentClears() {
when(paymentGateway.charge(100.0)).thenReturn(true);
assertTrue(orderService.placeOrder(100.0));
verify(paymentGateway, times(1)).charge(100.0);
}
}When JUnit creates an instance of OrderServiceTest, it sees a constructor with parameters. It asks MockitoExtension: "Can you resolve PaymentGateway annotated with @Mock?" Yes. "Can you resolve OrderRepository annotated with @Mock?" Yes. The mocks are created and passed into the constructor. Your field assignments run, and the test class is ready.
Advantages of constructor injection in tests
- Fields can be
final— no accidental reassignment between tests. - Mirrors how production code is structured (constructor injection best practice).
- Dependencies are explicit and visible at a glance.
Critical limitation: lifecycle must be @TestInstance(PER_METHOD)
The default JUnit 5 lifecycle is PER_METHOD — a new instance of the test class is created for every test method. This is safe because each test gets fresh mocks from a fresh constructor call.
If you switch to @TestInstance(Lifecycle.PER_CLASS), only one instance is created for all tests. That means the constructor runs once, mocks are created once, and all tests share the same mock objects. Stubbing from one test leaks into the next — a classic source of hard to diagnose failures.
java
// DANGEROUS combination: PER_CLASS + constructor @Mock
@TestInstance(Lifecycle.PER_CLASS) // <-- only one instance for all tests
@ExtendWith(MockitoExtension.class)
class BadExample {
private final PaymentGateway paymentGateway;
BadExample(@Mock PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
// This mock is SHARED across all test methods — stubs leak!
}
}Rule: If you use constructor injection of mocks, keep the test lifecycle at PER_METHOD (the default). Never combine PER_CLASS with constructor parameter mocks.
You cannot use @InjectMocks with constructor parameter mocks
@InjectMocks works by first resolving all @Mock fields, then creating an instance of the subject class and injecting those mocks. When you move mocks to constructor parameters, this chain breaks: the mocks are resolved inside the constructor before @InjectMocks processing ever starts. You are now inside the constructor — you must create the object under test yourself.
java
// @InjectMocks is NOT available when using constructor parameters
OrderServiceTest(@Mock PaymentGateway pg, @Mock OrderRepository repo) {
// Must wire the subject manually
this.orderService = new OrderService(pg, repo);
}Using @Mock as a Test Method Parameter
You can also declare mocks directly as parameters of individual test methods:
java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
// No fields, no constructor — everything per method
@Test
void placeOrder_success(
@Mock PaymentGateway paymentGateway,
@Mock OrderRepository orderRepository) {
// Mocks are fresh for this method only
OrderService orderService = new OrderService(paymentGateway, orderRepository);
when(paymentGateway.charge(100.0)).thenReturn(true);
assertTrue(orderService.placeOrder(100.0));
verify(paymentGateway).charge(100.0);
}
@Test
void placeOrder_failsWhenPaymentDeclined(
@Mock PaymentGateway paymentGateway,
@Mock OrderRepository orderRepository) {
OrderService orderService = new OrderService(paymentGateway, orderRepository);
when(paymentGateway.charge(100.0)).thenReturn(false);
assertFalse(orderService.placeOrder(100.0));
}
}JUnit resolves each parameter via MockitoExtension.resolveParameter just before invoking the test method. The mocks exist only for the duration of that single test and are torn down afterward.
Disadvantage of method parameters
If the test class has 20 test methods, you must declare the mock parameters in every single method signature and create the object under test 20 times. This produces significant repetition. Field injection or constructor injection is more practical for classes with many tests.
Comparison of All Three Approaches
| Approach | @InjectMocks available | final fields | Lifecycle concern | Best for |
|---|---|---|---|---|
Field @Mock | Yes | No | PER_METHOD safe | Most cases; standard approach |
Constructor @Mock | No (manual wiring) | Yes | Must stay PER_METHOD | Final fields, mirrors production style |
Method parameter @Mock | No (manual wiring) | N/A | None (fresh per method) | One-off tests, isolation demos |
Full Working Example
java
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
// Standard approach (recommended for most cases)
@ExtendWith(MockitoExtension.class)
class OrderServiceFieldTest {
@Mock PaymentGateway paymentGateway;
@Mock OrderRepository orderRepository;
@InjectMocks OrderService orderService;
@Test
void placeOrder_chargesCorrectAmount() {
when(paymentGateway.charge(50.0)).thenReturn(true);
orderService.placeOrder(50.0);
verify(paymentGateway).charge(50.0);
}
}
// Constructor injection approach
@ExtendWith(MockitoExtension.class)
class OrderServiceConstructorTest {
private final OrderService orderService;
private final PaymentGateway paymentGateway;
OrderServiceConstructorTest(
@Mock PaymentGateway paymentGateway,
@Mock OrderRepository orderRepository) {
this.paymentGateway = paymentGateway;
// Manual wiring required — no @InjectMocks here
this.orderService = new OrderService(paymentGateway, orderRepository);
}
@Test
void placeOrder_usesPaymentGateway() {
when(paymentGateway.charge(75.0)).thenReturn(true);
orderService.placeOrder(75.0);
verify(paymentGateway).charge(75.0);
}
}
// Method parameter approach
@ExtendWith(MockitoExtension.class)
class OrderServiceMethodParamTest {
@Test
void placeOrder_returnsFalseWhenDeclined(
@Mock PaymentGateway paymentGateway,
@Mock OrderRepository orderRepository) {
OrderService svc = new OrderService(paymentGateway, orderRepository);
when(paymentGateway.charge(anyDouble())).thenReturn(false);
assertFalse(svc.placeOrder(200.0));
}
}Interview Questions & Pitfalls
Q1: What is ParameterResolver in JUnit 5 and how does MockitoExtension use it?
ParameterResolver is a JUnit 5 extension interface with two methods: supportsParameter and resolveParameter. When JUnit needs to invoke a constructor or test method that has parameters, it queries all registered extensions that implement ParameterResolver. MockitoExtension implements this interface and returns true from supportsParameter for any parameter annotated with @Mock, @Spy, or @Captor. In resolveParameter it creates and returns the appropriate Mockito object.
Q2: Can you use @InjectMocks together with constructor parameter mocks?
No. @InjectMocks relies on first resolving all @Mock fields, then creating the subject instance. When mocks are declared as constructor parameters, the mock resolution happens inside the constructor before @InjectMocks processing starts. The automatic injection chain is broken, so you must wire the object under test manually inside the constructor.
Q3: Why is @TestInstance(Lifecycle.PER_CLASS) dangerous when mocks are constructor parameters?
With PER_CLASS, a single test class instance serves all test methods. The constructor runs only once, so the mocks are created once and shared. Stubbing state from one test — call counts, configured return values — leaks into subsequent tests, leading to intermittent and order-dependent failures.
Q4: What is the main practical downside of declaring @Mock in test method parameters?
If there are many test methods, every method must redeclare all mock parameters and manually create the object under test. There is no reuse. For a class with 15 test methods, this means 15 identical constructor calls and 15 sets of parameter declarations. Field or constructor injection is far less repetitive for real test suites.
Q5: When would you actually prefer constructor parameter mocks over field mocks?
When you want final fields in the test class — making the test more predictable and preventing accidental reassignment — or when you want the test class to mirror production code style (constructor injection is preferred in Spring applications). It also makes all dependencies of the test class obvious at the constructor signature without scanning all fields.
Q6: Does MockitoExtension handle teardown when mocks are constructor or method parameters?
Yes. MockitoExtension also implements AfterEachCallback. After each test method it closes the Mockito session, releasing thread local state. This applies regardless of how mocks were created — via field, constructor, or method parameters. You do not need to call any cleanup code manually.