Mockito @ExtendWith

🎓 Top 15 Udemy Courses (80-90% Discount): My Udemy Courses - Ramesh Fadatare — All my Udemy courses are real-time and project oriented courses.

▶️ Subscribe to My YouTube Channel (178K+ subscribers): Java Guides on YouTube

▶️ For AI, ChatGPT, Web, Tech, and Generative AI, subscribe to another channel: Ramesh Fadatare on YouTube

Introduction

The @ExtendWith annotation in JUnit 5 is used to register extensions. Mockito provides a MockitoExtension class that can be used with the @ExtendWith annotation to enable Mockito annotations and simplify the creation of mocks. This tutorial will demonstrate how to use the @ExtendWith annotation with MockitoExtension to mock dependencies in a PaymentService class.

Maven Dependencies

To use Mockito with JUnit 5, add the following dependencies to your pom.xml file:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>4.8.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>4.8.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.9.2</version>
    <scope>test</scope>
</dependency>

Example Scenario

We will create a PaymentService class that has a dependency on a PaymentRepository and a NotificationService. Our goal is to test the PaymentService methods using Mockito's @ExtendWith annotation to mock the dependencies.

PaymentService, PaymentRepository, and NotificationService Classes

First, create the Payment, PaymentRepository, and NotificationService classes.

public class Payment {
    private String transactionId;
    private double amount;

    // Constructor, getters, and setters
    public Payment(String transactionId, double amount) {
        this.transactionId = transactionId;
        this.amount = amount;
    }

    public String getTransactionId() {
        return transactionId;
    }

    public void setTransactionId(String transactionId) {
        this.transactionId = transactionId;
    }

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }
}

public interface PaymentRepository {
    void savePayment(Payment payment);
    Payment findPaymentByTransactionId(String transactionId);
}

public class NotificationService {
    public void notifyCustomer(String message) {
        // Code to notify customer
    }
}

public class PaymentService {
    private final PaymentRepository paymentRepository;
    private final NotificationService notificationService;

    public PaymentService(PaymentRepository paymentRepository, NotificationService notificationService) {
        this.paymentRepository = paymentRepository;
        this.notificationService = notificationService;
    }

    public void processPayment(Payment payment) {
        paymentRepository.savePayment(payment);
        notificationService.notifyCustomer("Payment processed: " + payment.getTransactionId());
    }

    public Payment getPayment(String transactionId) {
        return paymentRepository.findPaymentByTransactionId(transactionId);
    }
}

JUnit 5 Test Class with Mockito

Create a test class for PaymentService using JUnit 5 and Mockito.

import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MockitoExtension.class)
public class PaymentServiceTest {

    @Mock
    private PaymentRepository paymentRepository;

    @Mock
    private NotificationService notificationService;

    @InjectMocks
    private PaymentService paymentService;

    @Captor
    private ArgumentCaptor<Payment> paymentCaptor;

    @Test
    public void testProcessPayment() {
        // Given
        Payment payment = new Payment("12345", 100.0);

        // When
        paymentService.processPayment(payment);

        // Then
        verify(paymentRepository).savePayment(paymentCaptor.capture());
        Payment capturedPayment = paymentCaptor.getValue();
        assertEquals("12345", capturedPayment.getTransactionId());
        assertEquals(100.0, capturedPayment.getAmount());
        verify(notificationService).notifyCustomer("Payment processed: 12345");
    }

    @Test
    public void testGetPayment() {
        // Given
        Payment mockPayment = new Payment("12345", 100.0);
        when(paymentRepository.findPaymentByTransactionId("12345")).thenReturn(mockPayment);

        // When
        Payment result = paymentService.getPayment("12345");

        // Then
        assertNotNull(result);
        assertEquals("12345", result.getTransactionId());
        assertEquals(100.0, result.getAmount());
        verify(paymentRepository).findPaymentByTransactionId("12345");
    }
}

Explanation of Annotations

  1. @ExtendWith(MockitoExtension.class):

    • This annotation integrates Mockito with JUnit 5. It tells JUnit to enable Mockito-specific features and process annotations such as @Mock, @InjectMocks, and @Captor.
    • By using MockitoExtension, you don't need to initialize the mocks manually. The extension takes care of creating and injecting mock instances.
  2. @Mock:

    • This annotation creates a mock instance of the specified class or interface. In this example, paymentRepository and notificationService are mocked.
    • Mocks are useful for simulating dependencies in isolation. They allow you to define behavior and verify interactions without needing real implementations.
  3. @InjectMocks:

    • This annotation injects the mocks marked with @Mock into the class under test. In this case, paymentRepository and notificationService are injected into PaymentService.
    • This is particularly useful for classes that have dependencies, as it ensures the class under test receives mock instances instead of real dependencies.
  4. @Captor:

    • This annotation creates an ArgumentCaptor for capturing arguments passed to mock methods. In this example, paymentCaptor captures the Payment object passed to savePayment.
    • Argument captors are useful for verifying the values of arguments passed to mocked methods, providing more insight into the interactions between objects.

Test Methods

  1. testProcessPayment:

    • Given: A new Payment object is created with a transaction ID and amount.
    • When: The processPayment method of paymentService is called with the payment object.
    • Then:
      • Verifies that the savePayment method was called on paymentRepository with a Payment object.
      • Captures the Payment object passed to savePayment using paymentCaptor.
      • Asserts that the captured payment's transaction ID and amount are correct.
      • Verifies that the notifyCustomer method was called on notificationService with the expected message.
  2. testGetPayment:

    • Given: A mock Payment object is created and configured to be returned by paymentRepository when the findPaymentByTransactionId method is called with a specific transaction ID.
    • When: The getPayment method of paymentService is called with the transaction ID.
    • Then:
      • Asserts that the returned payment is not null and that its transaction ID and amount are correct.
      • Verifies that the findPaymentByTransactionId method was called on paymentRepository with the expected transaction ID.

Conclusion

The @ExtendWith annotation in JUnit 5, combined with MockitoExtension, simplifies the creation and injection of mock objects in unit tests. By using @Mock, @InjectMocks, and @Captor, you can easily set up mock dependencies and focus on testing the behavior of your code. This step-by-step guide demonstrated how to effectively use the @ExtendWith annotation with MockitoExtension in your unit tests, covering different scenarios to ensure comprehensive testing of the PaymentService class.

Related Mockito Annotations

My Top and Bestseller Udemy Courses. The sale is going on with a 70 - 80% discount. The discount coupon has been added to each course below:

Comments

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare