Mockito thenThrow()

Introduction

The thenThrow() method in Mockito is used to specify that a method call on a mock object should throw an exception. This is particularly useful when you want to test how your code handles exceptions from dependencies. This tutorial will demonstrate how to use the thenThrow() method in Mockito to configure mock objects to throw exceptions.

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. Our goal is to test how the PaymentService handles exceptions thrown by the PaymentRepository using the thenThrow() method in Mockito.

PaymentService and PaymentRepository Classes

First, create the Payment class, the PaymentRepository interface, and the PaymentService class.

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) throws Exception;
    Payment findPaymentByTransactionId(String transactionId) throws Exception;
}

public class PaymentService {
    private final PaymentRepository paymentRepository;

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

    public void processPayment(Payment payment) throws Exception {
        paymentRepository.savePayment(payment);
    }

    public Payment getPayment(String transactionId) throws Exception {
        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.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;

    @InjectMocks
    private PaymentService paymentService;

    @Test
    public void testProcessPaymentThrowsException() {
        // Given
        Payment payment = new Payment("12345", 100.0);
        doThrow(new Exception("Payment failed")).when(paymentRepository).savePayment(payment);

        // When & Then
        Exception exception = assertThrows(Exception.class, () -> {
            paymentService.processPayment(payment);
        });
        assertEquals("Payment failed", exception.getMessage());
    }

    @Test
    public void testGetPaymentThrowsException() {
        // Given
        String transactionId = "12345";
        doThrow(new Exception("Payment not found")).when(paymentRepository).findPaymentByTransactionId(transactionId);

        // When & Then
        Exception exception = assertThrows(Exception.class, () -> {
            paymentService.getPayment(transactionId);
        });
        assertEquals("Payment not found", exception.getMessage());
    }
}

Explanation

  1. Creating Mocks with @Mock:

    • The @Mock annotation creates a mock instance of the PaymentRepository interface.
    • This mock instance can be used to simulate the behavior of the PaymentRepository in a controlled way.
  2. Injecting Mocks with @InjectMocks:

    • The @InjectMocks annotation injects the mock PaymentRepository into the PaymentService instance to provide a controlled test environment.
    • This allows the PaymentService methods to be tested in isolation from the actual PaymentRepository implementation.
  3. Configuring Mock Behavior with thenThrow():

    • The doThrow(new Exception("Payment failed")).when(paymentRepository).savePayment(payment); method configures the mock PaymentRepository to throw an Exception with the message "Payment failed" when the savePayment method is called with the specified Payment object.
    • Similarly, the doThrow(new Exception("Payment not found")).when(paymentRepository).findPaymentByTransactionId(transactionId); method configures the mock PaymentRepository to throw an Exception with the message "Payment not found" when the findPaymentByTransactionId method is called with the specified transaction ID.
  4. Testing Configured Behavior:

    • The testProcessPaymentThrowsException() method tests the processPayment method of the PaymentService class. The thenThrow() method ensures that the mock PaymentRepository throws the expected exception, allowing the exception handling behavior of the processPayment method to be verified.
    • The testGetPaymentThrowsException() method tests the getPayment method of the PaymentService class. The thenThrow() method ensures that the mock PaymentRepository throws the expected exception, allowing the exception handling behavior of the getPayment method to be verified.

Additional Scenarios

Scenario: Handling Different Exceptions

In this scenario, we will demonstrate how to configure different behaviors for a mock method using the thenThrow() method.

@Test
public void testDifferentExceptions() {
    // Given
    Payment payment = new Payment("12345", 100.0);
    doThrow(new IllegalArgumentException("Invalid payment amount")).when(paymentRepository).savePayment(payment);

    // When & Then
    IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
        paymentService.processPayment(payment);
    });
    assertEquals("Invalid payment amount", exception.getMessage());
}

Explanation

  1. Configuring Different Exceptions:

    • The doThrow(new IllegalArgumentException("Invalid payment amount")).when(paymentRepository).savePayment(payment); method configures the mock PaymentRepository to throw an IllegalArgumentException with the message "Invalid payment amount" when the savePayment method is called with the specified Payment object.
  2. Testing Configured Behaviors:

    • The testDifferentExceptions() method tests the processPayment method of the PaymentService class with a different exception type to verify that the mock PaymentRepository throws the expected exception.

Conclusion

The thenThrow() method in Mockito simplifies the configuration of mock objects to throw exceptions for unit testing. By using thenThrow(), you can easily define the behavior of mock methods when exceptions need to be tested, allowing you to verify the exception handling logic in your code. This step-by-step guide demonstrated how to effectively use the thenThrow() method in your unit tests, covering different scenarios to ensure comprehensive testing of the PaymentService class.

Related Mockito Methods

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