Hello! Let's continue our journey into testing Spring applications.
In our last lesson, we wrote our first unit tests for the service layer. We saw how to use the basic structure of a Mockito test with @ExtendWith, @Mock, and @InjectMocks to isolate our service from its dependencies. This allowed us to test business logic for both successful outcomes and expected exceptions.
Today, we'll build directly on that foundation to fully master the learning outcome: Mock dependencies in unit tests using @Mock and @InjectMocks. We will move beyond just using these annotations to deeply understanding how and why they work. This deeper knowledge is crucial for writing clean, maintainable, and effective tests, especially as your applications grow in complexity.
1. The "Why": From Manual Instantiation to Declarative Mocks
In the previous lesson, we immediately used annotations. But to appreciate their value, let's see what they replace. Without annotations, you would have to create and manage mocks manually in every test or in a setup method.
For example, to create a mock UserRepository, you would write:
UserRepository userRepository = Mockito.mock(UserRepository.class);
While this works, it can become repetitive and verbose as you add more dependencies. The @Mock annotation is a more declarative and readable way to achieve the same result.
@Mock
private UserRepository userRepository;
This tells Mockito, "I want a mock object for this field." Mockito then handles the creation for us. This approach has several advantages that contribute to better tests in the long run.

For these annotations to work, we need to tell Mockito to process them. As we've seen, @ExtendWith(MockitoExtension.class) is the standard way to do this in JUnit 5. It acts as the engine that finds and initializes all the fields annotated with @Mock, @InjectMocks, etc., before our tests run.
Getting Started with Mockito @Mock, @Spy, @Captor and ...
The Baeldung article 'Getting Started with Mockito Annotations' provides a good comparison between the manual and annotated approaches. It also covers the different ways to initialize Mockito annotations.
Please read section '2. Enable Mockito Annotations' and section '3. @Mock Annotation'. Focus on the code examples that contrast Mockito.mock() with @Mock. Note the different initialization options listed in section 2, but remember we'll be sticking with @ExtendWith(MockitoExtension.class) as it's the modern standard for JUnit 5.
2. The "How": A Practical Look at Injecting Mocks
Simply creating a mock with @Mock is only half the story. The real goal is to get that mock object inside the service instance we are testing. This is the job of @InjectMocks.
Let's walk through a common scenario step-by-step to see why @InjectMocks is so critical. The following video provides an excellent, practical demonstration.
Unit Testing in Spring Boot with JUnit 5 and Mockito | Part 1
The 'CodeSnippet' channel has a great video that builds a service test from scratch. We'll watch a few key segments that perfectly illustrate the problem of uninitialized dependencies and how @Mock and @InjectMocks work together to solve it.
Please watch the video in three parts: The Problem (09:27 - 15:25): Pay attention to the NullPointerException that occurs when the test tries to call a method on the service. This happens because the service's internal dependency (the repository) is null. Creating the Mocks (15:25 - 17:48): The video introduces @Mock to create a mock repository, but shows that this alone doesn't fix the problem. The dependency inside the service is still null. Injecting the Mocks (17:48 - 20:48): This is the key part. See how adding @InjectMocks to the service instance finally resolves the NullPointerException. The presenter uses the debugger to show that the mock repository has now been successfully injected into the service.
As you saw in the video, the annotations work in tandem:
@ExtendWith(MockitoExtension.class)activates Mockito.@Mockcreates mock instances of your dependencies (e.g.,ProductRepository).@InjectMockscreates a real instance of your class under test (e.g.,ProductService) and scans for dependencies it can fulfill using the created mocks.
3. Understanding Injection Strategies
So, how does @InjectMocks decide where to put the mocks? It's not magic. Mockito uses a specific order of strategies to perform the injection:
- Constructor Injection: Mockito looks for the "greediest" constructor (the one with the most parameters) it can satisfy with the available mocks. If it finds one, it will instantiate your class using that constructor, passing the mocks as arguments.
- Setter Injection: If constructor injection fails (e.g., there's no suitable constructor or only a no-arg constructor), Mockito will then look for setter methods that match the types of your mocks. For a field named
userRepository, it would look for asetUserRepository(...)method. - Field Injection: If neither of the above works, Mockito will attempt to inject the mocks directly into the fields, even if they are
private.
Constructor injection is the preferred method in modern software design because it makes dependencies explicit and allows for creating immutable objects. By designing your services to take their dependencies in the constructor, you make your code clearer and your tests more straightforward.
Unit tests with Mockito - Tutorial
This article from vogella provides a concise explanation of how @InjectMocks works and its injection strategies.
Read section '8. Using @InjectMocks for dependency injection via Mockito'. The key takeaway is the final paragraph explaining the order of injection: constructor, then setter, then property.
Here's an example of a test setup that relies on constructor injection:
// The service we want to test
@Service
public class OrderService {
private final ProductRepository productRepository;
private final NotificationService notificationService;
// Constructor injection
public OrderService(ProductRepository productRepository, NotificationService notificationService) {
this.productRepository = productRepository;
this.notificationService = notificationService;
}
// ... business logic
}
// The test class
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private ProductRepository productRepository; // Mock dependency 1
@Mock
private NotificationService notificationService; // Mock dependency 2
@InjectMocks
private OrderService orderService; // Mockito will create an OrderService and inject the two mocks via the constructor.
@Test
void testPlaceOrder() {
// ... Arrange, Act, Assert
}
}
Test your understanding!
You are writing a test for a PaymentService. This service depends on a PaymentGateway and a FraudDetector. The PaymentService class has a single constructor: public PaymentService(PaymentGateway gateway, FraudDetector detector).
Write the field declarations and annotations for your test class, PaymentServiceTest, to correctly set up the mocks and the service instance for testing.
Show answer
Your test class setup should look like this:
@ExtendWith(MockitoExtension.class)
class PaymentServiceTest {
@Mock
private PaymentGateway paymentGateway;
@Mock
private FraudDetector fraudDetector;
@InjectMocks
private PaymentService paymentService;
// ... your test methods
}
Here, @Mock creates the mock dependencies, and @InjectMocks creates an instance of PaymentService, automatically using its constructor to inject the paymentGateway and fraudDetector mocks.
Conclusion
In this lesson, we took a much deeper look into the mechanics of mocking with annotations. You now understand not just what @Mock and @InjectMocks do, but also why they are beneficial and how they work together to create an isolated testing environment for your service layer.
Key Takeaways:
- Mockito annotations like
@Mockand@InjectMockssignificantly reduce boilerplate code and improve the readability of your tests. - Annotations must be initialized. For JUnit 5,
@ExtendWith(MockitoExtension.class)is the standard mechanism. @InjectMocksintelligently injects fields annotated with@Mockinto your class under test.- Mockito prioritizes constructor injection, falling back to setter and then field injection. Aligning your code with constructor injection is a best practice.
What's Next?
We've now thoroughly covered how to unit test the service layer in isolation. But a backend application has multiple layers. In our next lesson, "Write integration tests for the web layer using @WebMvcTest and MockMvc," we will move up the stack. You'll learn how to test your REST controllers, which handle incoming HTTP requests. We will see how the skills you've just mastered—mocking dependencies—are essential for testing the web layer without needing a running service layer.