Mockito BDDMockito willThrow()

Introduction

BDDMockito.willThrow() is a method provided by Mockito to support the Behavior-Driven Development (BDD) style of writing tests. It 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 BDDMockito.willThrow() to mock exceptions in a BDD style.

Maven Dependencies

To use Mockito with JUnit 5 and enable BDDMockito syntax, 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 TransactionRepository. Our goal is to test the PaymentService methods using BDDMockito.willThrow() to handle exceptions.

PaymentService and TransactionRepository Classes

First, create the Transaction class, the TransactionRepository interface, and the PaymentService class.

public 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 interface TransactionRepository {
    void saveTransaction(Transaction transaction) throws Exception;
    Transaction findTransactionById(String id) throws Exception;
}

public class PaymentService {
    private final TransactionRepository transactionRepository;

    public PaymentService(TransactionRepository transactionRepository) {
        this.transactionRepository = transactionRepository;
    }

    public void processPayment(String id, double amount) throws Exception {
        Transaction transaction = new Transaction(id, amount);
        transactionRepository.saveTransaction(transaction);
    }

    public Transaction getTransactionById(String id) throws Exception {
        return transactionRepository.findTransactionById(id);
    }
}

JUnit 5 Test Class with BDDMockito

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

import static org.mockito.BDDMockito.*;
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 TransactionRepository transactionRepository;

    @InjectMocks
    private PaymentService paymentService;

    @Test
    public void testProcessPaymentThrowsException() {
        // Given
        String id = "123";
        double amount = 100.0;
        willThrow(new RuntimeException("Transaction failed")).given(transactionRepository).saveTransaction(any(Transaction.class));

        // When & Then
        RuntimeException exception = assertThrows(RuntimeException.class, () -> {
            paymentService.processPayment(id, amount);
        });
        assertEquals("Transaction failed", exception.getMessage());
    }

    @Test
    public void testGetTransactionByIdThrowsException() {
        // Given
        String id = "123";
        willThrow(new RuntimeException("Transaction not found")).given(transactionRepository).findTransactionById(id);

        // When & Then
        RuntimeException exception = assertThrows(RuntimeException.class, () -> {
            paymentService.getTransactionById(id);
        });
        assertEquals("Transaction not found", exception.getMessage());
    }
}

Explanation

  1. Creating Mocks with @Mock:

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

    • The @InjectMocks annotation injects the mock TransactionRepository into the PaymentService instance to provide a controlled test environment. This allows the PaymentService methods to be tested in isolation from the actual TransactionRepository implementation.
  3. Using BDDMockito:

    • willThrow(): The willThrow(new RuntimeException("Transaction failed")).given(transactionRepository).saveTransaction(any(Transaction.class)); method configures the mock TransactionRepository to throw a RuntimeException when the saveTransaction method is called with any Transaction object. This allows the processPayment method of the PaymentService class to be tested with controlled exception handling behavior.
  4. Verifying Exceptions:

    • The assertThrows(RuntimeException.class, () -> { ... }); method verifies that the processPayment and getTransactionById methods throw a RuntimeException with the specified message when the saveTransaction and findTransactionById methods are called, respectively.

Additional Scenarios

Scenario: Handling Specific Exceptions

In this scenario, we will demonstrate how to handle specific exceptions using BDDMockito.willThrow().

@Test
public void testProcessPaymentThrowsSpecificException() {
    // Given
    String id = "123";
    double amount = 100.0;
    willThrow(new IllegalArgumentException("Invalid transaction ID")).given(transactionRepository).saveTransaction(any(Transaction.class));

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

Explanation

  1. Handling Specific Exceptions with BDDMockito:
    • The willThrow(new IllegalArgumentException("Invalid transaction ID")).given(transactionRepository).saveTransaction(any(Transaction.class)); method configures the mock TransactionRepository to throw an IllegalArgumentException when the saveTransaction method is called with any Transaction object. This allows the processPayment method of the PaymentService class to be tested with controlled exception handling behavior.

Scenario: Verifying No More Interactions

In this scenario, we will demonstrate how to verify that no more interactions occurred with the mock object using BDDMockito.

@Test
public void testNoMoreInteractionsWithTransactionRepository() {
    // Given
    String id = "123";
    double amount = 100.0;

    // When
    paymentService.processPayment(id, amount);

    // Then
    then(transactionRepository).should().saveTransaction(any(Transaction.class));
    then(transactionRepository).shouldHaveNoMoreInteractions();
}

Explanation

  1. Verifying No More Interactions:
    • The then(transactionRepository).shouldHaveNoMoreInteractions(); method verifies that no more interactions occurred with the TransactionRepository mock object after the saveTransaction method was called. This ensures that no unintended interactions happened with the TransactionRepository.

Conclusion

Using BDDMockito.willThrow() in Mockito allows you to write more readable and expressive tests that follow the Behavior-Driven Development (BDD) style. By using willThrow() for stubbing methods to throw exceptions, you can handle various scenarios and control the behavior of mock objects. This step-by-step guide demonstrated how to effectively use BDDMockito.willThrow() in your unit tests, covering different scenarios to ensure comprehensive testing of the PaymentService class.

Related Mockito BDDMockito Class Methods (Behavior-Driven Development Style)

Mockito BDDMockito
Mockito BDDMockito given()
Mockito BDDMockito willThrow()
Mockito BDDMockito willAnswer()
Mockito BDDMockito willReturn()
Mockito BDDMockito willDoNothing()
Mockito BDDMockito willCallRealMethod()
Mockito BDDMockito then()
Mockito BDDMockito.any()
Mockito BDDMockito.times()

Comments