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
@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.
- This annotation integrates Mockito with JUnit 5. It tells JUnit to enable Mockito-specific features and process annotations such as
@Mock
:- This annotation creates a mock instance of the specified class or interface. In this example,
paymentRepository
andnotificationService
are mocked. - Mocks are useful for simulating dependencies in isolation. They allow you to define behavior and verify interactions without needing real implementations.
- This annotation creates a mock instance of the specified class or interface. In this example,
@InjectMocks
:- This annotation injects the mocks marked with
@Mock
into the class under test. In this case,paymentRepository
andnotificationService
are injected intoPaymentService
. - This is particularly useful for classes that have dependencies, as it ensures the class under test receives mock instances instead of real dependencies.
- This annotation injects the mocks marked with
@Captor
:- This annotation creates an
ArgumentCaptor
for capturing arguments passed to mock methods. In this example,paymentCaptor
captures thePayment
object passed tosavePayment
. - Argument captors are useful for verifying the values of arguments passed to mocked methods, providing more insight into the interactions between objects.
- This annotation creates an
Test Methods
testProcessPayment
:- Given: A new
Payment
object is created with a transaction ID and amount. - When: The
processPayment
method ofpaymentService
is called with the payment object. - Then:
- Verifies that the
savePayment
method was called onpaymentRepository
with aPayment
object. - Captures the
Payment
object passed tosavePayment
usingpaymentCaptor
. - Asserts that the captured payment's transaction ID and amount are correct.
- Verifies that the
notifyCustomer
method was called onnotificationService
with the expected message.
- Verifies that the
- Given: A new
testGetPayment
:- Given: A mock
Payment
object is created and configured to be returned bypaymentRepository
when thefindPaymentByTransactionId
method is called with a specific transaction ID. - When: The
getPayment
method ofpaymentService
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 onpaymentRepository
with the expected transaction ID.
- Given: A mock
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.
Comments
Post a Comment
Leave Comment