Introduction
In this chapter, we will learn about the @InjectMocks
annotation in Mockito. This annotation is used to automatically inject mock objects into the class under test. It simplifies the process of setting up dependencies, making tests cleaner and more maintainable.
Key Points about the @InjectMocks Annotation
- Automatic Injection: Automatically injects mock objects into the class under test.
- Simplifies Setup: Reduces boilerplate code by eliminating the need for manual injection.
- Used with @Mock: Typically used in conjunction with the
@Mock
annotation. - Initialization: Requires initialization using
MockitoAnnotations.openMocks(this)
.
Setting Up Mockito for @InjectMocks Annotation
Ensure that you have the necessary dependencies in your pom.xml
if you are using Maven:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<!-- JUnit 5 dependency -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
Example: CalculatorService Class and CalculatorServiceTest Class
Description
In this example, we will create a CalculatorService
class that depends on a MathService
class. The CalculatorService
class will use the MathService
class to perform addition and subtraction operations. We will create unit tests for the CalculatorService
class using Mockito’s @InjectMocks
annotation to inject the mock MathService
class.
Class Under Test: CalculatorService
public class CalculatorService {
private final MathService mathService;
public CalculatorService(MathService mathService) {
this.mathService = mathService;
}
public int add(int a, int b) {
return mathService.add(a, b);
}
public int subtract(int a, int b) {
return mathService.subtract(a, b);
}
}
Supporting Class: MathService
public class MathService {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
}
Test Class: CalculatorServiceTest
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.junit.jupiter.api.Assertions.*;
public class CalculatorServiceTest {
@Mock
private MathService mathService;
@InjectMocks
private CalculatorService calculatorService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testAdd_Success() {
// Arrange
when(mathService.add(10, 20)).thenReturn(30);
// Act
int result = calculatorService.add(10, 20);
// Assert
assertEquals(30, result);
verify(mathService).add(10, 20);
}
@Test
void testSubtract_Success() {
// Arrange
when(mathService.subtract(20, 10)).thenReturn(10);
// Act
int result = calculatorService.subtract(20, 10);
// Assert
assertEquals(10, result);
verify(mathService).subtract(20, 10);
}
}
Explanation
- Mock Annotation:
@Mock private MathService mathService;
The
@Mock
annotation creates a mock object for theMathService
class. - InjectMocks Annotation:
@InjectMocks private CalculatorService calculatorService;
The
@InjectMocks
annotation creates an instance of theCalculatorService
class and injects theMathService
mock into it. - Initializing Mocks:
@BeforeEach void setUp() { MockitoAnnotations.openMocks(this); }
The
MockitoAnnotations.openMocks(this)
method initializes the mock objects and injects them into the class under test. This is typically done in a@BeforeEach
method to ensure mocks are reset before each test. - Test Method:
@Test void testAdd_Success() { // Arrange when(mathService.add(10, 20)).thenReturn(30); // Act int result = calculatorService.add(10, 20); // Assert assertEquals(30, result); verify(mathService).add(10, 20); }
This test method sets up the mock behavior (Arrange), calls the method under test (Act), and checks the expected results (Assert). It verifies that the mock’s method was called with the expected arguments.
Real-World Example: OrderService Class and OrderServiceTest Class
Class Under Test: OrderService
public class OrderService {
private final PaymentService paymentService;
private final InventoryService inventoryService;
public OrderService(PaymentService paymentService, InventoryService inventoryService) {
this.paymentService = paymentService;
this.inventoryService = inventoryService;
}
public boolean placeOrder(String productId, int quantity) {
if (inventoryService.checkStock(productId, quantity)) {
paymentService.processPayment(productId, quantity);
return true;
}
return false;
}
}
Supporting Classes: PaymentService and InventoryService
public class PaymentService {
public void processPayment(String productId, int quantity) {
// Logic to process payment
}
}
public class InventoryService {
public boolean checkStock(String productId, int quantity) {
// Logic to check stock
return true;
}
}
Test Class: OrderServiceTest
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.junit.jupiter.api.Assertions.*;
public class OrderServiceTest {
@Mock
private PaymentService paymentService;
@Mock
private InventoryService inventoryService;
@InjectMocks
private OrderService orderService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testPlaceOrder_Success() {
// Arrange
when(inventoryService.checkStock("product123", 2)).thenReturn(true);
// Act
boolean result = orderService.placeOrder("product123", 2);
// Assert
assertTrue(result);
verify(inventoryService).checkStock("product123", 2);
verify(paymentService).processPayment("product123", 2);
}
@Test
void testPlaceOrder_Failure() {
// Arrange
when(inventoryService.checkStock("product123", 2)).thenReturn(false);
// Act
boolean result = orderService.placeOrder("product123", 2);
// Assert
assertFalse(result);
verify(inventoryService).checkStock("product123", 2);
verify(paymentService, never()).processPayment(anyString(), anyInt());
}
}
Explanation
- Mock Annotation:
@Mock private PaymentService paymentService; @Mock private InventoryService inventoryService;
The
@Mock
annotation creates mock objects for thePaymentService
andInventoryService
classes. - InjectMocks Annotation:
@InjectMocks private OrderService orderService;
The
@InjectMocks
annotation creates an instance of theOrderService
class and injects the mocks into it. - Initializing Mocks:
@BeforeEach void setUp() { MockitoAnnotations.openMocks(this); }
The
MockitoAnnotations.openMocks(this)
method initializes the mock objects and injects them into the class under test. - Test Method:
@Test void testPlaceOrder_Success() { // Arrange when(inventoryService.checkStock("product123", 2)).thenReturn(true); // Act boolean result = orderService.placeOrder("product123", 2); // Assert assertTrue(result); verify(inventoryService).checkStock("product123", 2); verify(paymentService).processPayment("product123", 2); }
This test method sets up the mock behavior (Arrange), calls the method under test (Act), and checks the expected results (Assert). It verifies that the dependencies are correctly injected and used.
Conclusion
The @InjectMocks
annotation in Mockito simplifies the setup of tests by automatically injecting mock objects into the class under test. By using the @Mock
and @InjectMocks
annotations together, you can easily create and inject mock dependencies, making your tests cleaner and more maintainable. In this chapter, we demonstrated how to use Mockito to inject mocks with a simple CalculatorService
example and a real-world OrderService
example. This methodĀ helps improve the readability and maintainability of your test suite.