Mockito doThrow()

Introduction

The doThrow() 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 doThrow() 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 BankService class that has a dependency on a BankRepository. Our goal is to test how the BankService handles exceptions thrown by the BankRepository using the doThrow() method in Mockito.

BankService and BankRepository Classes

First, create the Account class, the BankRepository interface, and the BankService class.

public class Account {
    private String accountNumber;
    private double balance;

    // Constructor, getters, and setters
    public Account(String accountNumber, double balance) {
        this.accountNumber = accountNumber;
        this.balance = balance;
    }

    public String getAccountNumber() {
        return accountNumber;
    }

    public void setAccountNumber(String accountNumber) {
        this.accountNumber = accountNumber;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
}

public interface BankRepository {
    Account findAccountByNumber(String accountNumber) throws Exception;
    void withdraw(String accountNumber, double amount) throws Exception;
}

public class BankService {
    private final BankRepository bankRepository;

    public BankService(BankRepository bankRepository) {
        this.bankRepository = bankRepository;
    }

    public Account getAccountDetails(String accountNumber) throws Exception {
        return bankRepository.findAccountByNumber(accountNumber);
    }

    public void withdrawAmount(String accountNumber, double amount) throws Exception {
        bankRepository.withdraw(accountNumber, amount);
    }
}

JUnit 5 Test Class with Mockito

Create a test class for BankService 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 BankServiceTest {

    @Mock
    private BankRepository bankRepository;

    @InjectMocks
    private BankService bankService;

    @Test
    public void testGetAccountDetails() throws Exception {
        // Given
        String accountNumber = "12345";
        Account account = new Account(accountNumber, 1000.0);
        when(bankRepository.findAccountByNumber(accountNumber)).thenReturn(account);

        // When
        Account result = bankService.getAccountDetails(accountNumber);

        // Then
        assertNotNull(result);
        assertEquals(accountNumber, result.getAccountNumber());
        assertEquals(1000.0, result.getBalance());
    }

    @Test
    public void testWithdrawAmountThrowsException() {
        // Given
        String accountNumber = "12345";
        doThrow(new Exception("Insufficient funds")).when(bankRepository).withdraw(accountNumber, 500.0);

        // When & Then
        Exception exception = assertThrows(Exception.class, () -> {
            bankService.withdrawAmount(accountNumber, 500.0);
        });
        assertEquals("Insufficient funds", exception.getMessage());
    }
}

Explanation

  1. Creating Mocks with @Mock:

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

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

    • The doThrow(new Exception("Insufficient funds")).when(bankRepository).withdraw(accountNumber, 500.0); method configures the mock BankRepository to throw an Exception with the message "Insufficient funds" when the withdraw method is called with the specified account number and amount. This allows the withdrawAmount method of the BankService class to be tested with controlled exception handling behavior from the BankRepository.
  4. Testing Configured Behavior:

    • The testWithdrawAmountThrowsException() method tests the withdrawAmount method of the BankService class. The doThrow() method ensures that the mock BankRepository throws the expected exception, allowing the exception-handling behavior of the withdrawAmount 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 doThrow() method.

@Test
public void testWithdrawAmountWithDifferentExceptions() {
    // Given
    String accountNumber = "12345";
    doThrow(new IllegalArgumentException("Invalid amount")).when(bankRepository).withdraw(accountNumber, -100.0);

    // When & Then
    IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
        bankService.withdrawAmount(accountNumber, -100.0);
    });
    assertEquals("Invalid amount", exception.getMessage());
}

Explanation

  1. Configuring Different Exceptions:

    • The doThrow(new IllegalArgumentException("Invalid amount")).when(bankRepository).withdraw(accountNumber, -100.0); method configures the mock BankRepository to throw an IllegalArgumentException with the message "Invalid amount" when the withdraw method is called with the specified account number and negative amount. This allows the withdrawAmount method of the BankService class to be tested with different exception handling scenarios.
  2. Testing Configured Behaviors:

    • The testWithdrawAmountWithDifferentExceptions() method tests the withdrawAmount method of the BankService class with a different exception type to verify that the mock BankRepository throws the expected exception.

Conclusion

The doThrow() method in Mockito simplifies the configuration of mock objects to throw exceptions for unit testing. By using doThrow(), 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 doThrow() method in your unit tests, covering different scenarios to ensure comprehensive testing of the BankService 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