Mockito Mocking Final Classes and Methods

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

  1. Adding mockito-inline Dependency:

    • The mockito-inline dependency allows Mockito to mock final classes and methods. This is necessary for the examples to work.
  2. Creating Mocks with @Mock:

    • The @Mock annotation creates a mock instance of the final Transaction class. This mock instance can be used to simulate the behavior of the Transaction class in a controlled way.
  3. Injecting Mocks with @InjectMocks:

    • The @InjectMocks annotation injects the mock Transaction into the PaymentService instance to provide a controlled test environment. This allows the PaymentService methods to be tested in isolation from the actual Transaction implementation.
  4. Stubbing Final Method with when():

    • The when(transaction.process()).thenReturn("Processed transaction with ID: 123"); method configures the mock Transaction to return a specific value when the process method is called. This allows the processPayment method of the PaymentService class to be tested with controlled behavior from the Transaction.
  5. Verifying Interactions with verify():

    • The verify(transaction, times(1)).process(); method checks if the process method was called exactly once on the Transaction mock object. This ensures that the processPayment method of the PaymentService class interacts with the Transaction correctly.

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

  1. Mocking Final Method with doThrow():
    • The doThrow(new RuntimeException("Processing failed")).when(transaction).process(); method configures the mock Transaction to throw a RuntimeException when the process method is called. This allows the processPayment method of the PaymentService class to be tested with controlled exception handling behavior from the Transaction.

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

  1. Mocking Final Method with Custom Behavior:
    • The doAnswer(invocation -> { return "Custom processed transaction with ID: 123"; }).when(transaction).process(); method configures the mock Transaction to execute custom behavior when the process method is called. This allows the processPayment method of the PaymentService class to be tested with customized behavior from the Transaction.

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