Mockito @InjectMocks Annotation

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

  1. Mock Annotation:
    @Mock
    private MathService mathService;
    

    The @Mock annotation creates a mock object for the MathService class.

  2. InjectMocks Annotation:
    @InjectMocks
    private CalculatorService calculatorService;
    

    The @InjectMocks annotation creates an instance of the CalculatorService class and injects the MathService mock into it.

  3. 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.

  4. 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

  1. Mock Annotation:
    @Mock
    private PaymentService paymentService;
    
    @Mock
    private InventoryService inventoryService;
    

    The @Mock annotation creates mock objects for the PaymentService and InventoryService classes.

  2. InjectMocks Annotation:
    @InjectMocks
    private OrderService orderService;
    

    The @InjectMocks annotation creates an instance of the OrderService class and injects the mocks into it.

  3. 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.

  4. 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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top