Introduction
Mockito, by default, cannot mock final classes and methods. However, with the introduction of the mockito-inline
artifact, it is possible to mock final classes and methods. This is particularly useful when you need to test code that interacts with third-party libraries or legacy code with final classes and methods. This tutorial will demonstrate how to mock final classes and methods in Mockito.
Maven Dependencies
To use Mockito with JUnit 5 and enable mocking of final classes and methods, 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.mockito</groupId>
<artifactId>mockito-inline</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 final Transaction
class. Our goal is to test the PaymentService
methods by mocking the final Transaction
class.
PaymentService and Transaction Classes
First, create the Transaction
and PaymentService
classes.
public final class Transaction {
private String id;
private double amount;
// Constructor, getters, and setters
public Transaction(String id, double amount) {
this.id = id;
this.amount = amount;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public final String process() {
// Simulate some complex processing
return "Processed transaction with ID: " + id;
}
}
public class PaymentService {
private final Transaction transaction;
public PaymentService(Transaction transaction) {
this.transaction = transaction;
}
public String processPayment() {
return transaction.process();
}
}
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.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 Transaction transaction;
@InjectMocks
private PaymentService paymentService;
@Test
public void testProcessPayment() {
// Given
when(transaction.process()).thenReturn("Processed transaction with ID: 123");
// When
String result = paymentService.processPayment();
// Then
assertEquals("Processed transaction with ID: 123", result);
verify(transaction, times(1)).process();
}
}
Explanation
Adding
mockito-inline
Dependency:- The
mockito-inline
dependency allows Mockito to mock final classes and methods. This is necessary for the examples to work.
- The
Creating Mocks with
@Mock
:- The
@Mock
annotation creates a mock instance of the finalTransaction
class. This mock instance can be used to simulate the behavior of theTransaction
class in a controlled way.
- The
Injecting Mocks with
@InjectMocks
:- The
@InjectMocks
annotation injects the mockTransaction
into thePaymentService
instance to provide a controlled test environment. This allows thePaymentService
methods to be tested in isolation from the actualTransaction
implementation.
- The
Stubbing Final Method with
when()
:- The
when(transaction.process()).thenReturn("Processed transaction with ID: 123");
method configures the mockTransaction
to return a specific value when theprocess
method is called. This allows theprocessPayment
method of thePaymentService
class to be tested with controlled behavior from theTransaction
.
- The
Verifying Interactions with
verify()
:- The
verify(transaction, times(1)).process();
method checks if theprocess
method was called exactly once on theTransaction
mock object. This ensures that theprocessPayment
method of thePaymentService
class interacts with theTransaction
correctly.
- The
Additional Scenarios
Scenario: Mocking Final Methods with doThrow()
In this scenario, we will demonstrate how to mock a final method to throw an exception using doThrow()
.
@Test
public void testProcessPaymentThrowsException() {
// Given
doThrow(new RuntimeException("Processing failed")).when(transaction).process();
// When & Then
RuntimeException exception = assertThrows(RuntimeException.class, () -> {
paymentService.processPayment();
});
assertEquals("Processing failed", exception.getMessage());
}
Explanation
- Mocking Final Method with
doThrow()
:- The
doThrow(new RuntimeException("Processing failed")).when(transaction).process();
method configures the mockTransaction
to throw aRuntimeException
when theprocess
method is called. This allows theprocessPayment
method of thePaymentService
class to be tested with controlled exception handling behavior from theTransaction
.
- The
Scenario: Mocking Final Methods with doAnswer()
In this scenario, we will demonstrate how to mock a final method with custom behavior using doAnswer()
.
@Test
public void testProcessPaymentWithCustomBehavior() {
// Given
doAnswer(invocation -> {
return "Custom processed transaction with ID: 123";
}).when(transaction).process();
// When
String result = paymentService.processPayment();
// Then
assertEquals("Custom processed transaction with ID: 123", result);
verify(transaction, times(1)).process();
}
Explanation
- Mocking Final Method with Custom Behavior:
- The
doAnswer(invocation -> { return "Custom processed transaction with ID: 123"; }).when(transaction).process();
method configures the mockTransaction
to execute custom behavior when theprocess
method is called. This allows theprocessPayment
method of thePaymentService
class to be tested with customized behavior from theTransaction
.
- The
Conclusion
Mocking final classes and methods in Mockito simplifies the configuration of method calls on mock objects for unit testing. By using the mockito-inline
dependency, you can handle various scenarios and control the behavior of final classes and methods. This step-by-step guide demonstrated how to effectively mock final classes and methods in your unit tests, covering different scenarios to ensure comprehensive testing of the PaymentService
class.
Related Mockito Posts
Mockito mock()
Mockito spy()
Mockito when()
Mockito thenThrow()
Mockito verify()
Mockito times()
Mockito never()
Mockito any()
Mockito eq()
Mockito inOrder()
Mockito doReturn()
Mockito doThrow()
Mockito doAnswer()
Mockito timeout()
Mockito ArgumentMatchers
Comments
Post a Comment
Leave Comment