Introduction
In this chapter, we will learn about injecting mocks using Mockito. Injecting mocks can simplify the process of setting up dependencies for the class under test. This approach allows you to automatically inject mocked dependencies into the class, making the tests cleaner and more maintainable.
What is Injecting Mocks?
Injecting mocks involves using annotations to automatically create and inject mock objects into the class under test. Mockito provides the @Mock
and @InjectMocks
annotations to facilitate this process. The @Mock
annotation creates a mock object, and the @InjectMocks
annotation creates an instance of the class under test and injects the mocks into it.
Setting Up Mockito for Injecting Mocks
Ensure that you have the necessary dependencies in your pom.xml
if you are using Maven:
<!-- 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>
Example: CalculatorService Class and CalculatorServiceTest 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
- Annotations:
@Mock private MathService mathService; @InjectMocks private CalculatorService calculatorService;
These annotations are used to create mock objects (
@Mock
) and inject them into the class under test (@InjectMocks
). - Injecting Mocks:
When Mockito sees the@InjectMocks
annotation, it creates an instance of theCalculatorService
class and injects theMathService
mock into it. This is how the dependencies are automatically injected. - Initializing Mocks:
@BeforeEach void setUp() { MockitoAnnotations.openMocks(this); }
This method initializes the mocks before each test method runs, ensuring that the
CalculatorService
instance has its dependencies injected. - 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 dependencies are correctly injected and used.
Output
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
- Annotations:
@Mock private PaymentService paymentService; @Mock private InventoryService inventoryService; @InjectMocks private OrderService orderService;
These annotations are used to create mock objects (
@Mock
) and inject them into the class under test (@InjectMocks
). - Injecting Mocks:
When Mockito sees the@InjectMocks
annotation, it creates an instance of theOrderService
class and injects thePaymentService
andInventoryService
mocks into it. This is how the dependencies are automatically injected. - Initializing Mocks:
@BeforeEach void setUp() { MockitoAnnotations.openMocks(this); }
This method initializes the mocks before each test method runs, ensuring that the
OrderService
instance has its dependencies injected. - 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
Injecting mocks using Mockito makes the setup of tests cleaner and more maintainable. By using the @Mock
and @InjectMocks
annotations, you can easily create and inject mock dependencies into the class under test. This approach simplifies the test code and ensures that the dependencies are properly mocked. 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 makes your tests more readable and maintainable, improving the overall quality of your test suite.