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
Creating Mocks with
@Mock
:- The
@Mock
annotation creates a mock instance of theTransactionRepository
interface. This mock instance can be used to simulate the behavior of theTransactionRepository
in a controlled way.
- The
Injecting Mocks with
@InjectMocks
:- The
@InjectMocks
annotation injects the mockTransactionRepository
into thePaymentService
instance to provide a controlled test environment. This allows thePaymentService
methods to be tested in isolation from the actualTransactionRepository
implementation.
- The
Using BDDMockito:
willThrow()
: ThewillThrow(new RuntimeException("Transaction failed")).given(transactionRepository).saveTransaction(any(Transaction.class));
method configures the mockTransactionRepository
to throw aRuntimeException
when thesaveTransaction
method is called with anyTransaction
object. This allows theprocessPayment
method of thePaymentService
class to be tested with controlled exception handling behavior.
Verifying Exceptions:
- The
assertThrows(RuntimeException.class, () -> { ... });
method verifies that theprocessPayment
andgetTransactionById
methods throw aRuntimeException
with the specified message when thesaveTransaction
andfindTransactionById
methods are called, respectively.
- The
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
- Handling Specific Exceptions with BDDMockito:
- The
willThrow(new IllegalArgumentException("Invalid transaction ID")).given(transactionRepository).saveTransaction(any(Transaction.class));
method configures the mockTransactionRepository
to throw anIllegalArgumentException
when thesaveTransaction
method is called with anyTransaction
object. This allows theprocessPayment
method of thePaymentService
class to be tested with controlled exception handling behavior.
- The
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
- Verifying No More Interactions:
- The
then(transactionRepository).shouldHaveNoMoreInteractions();
method verifies that no more interactions occurred with theTransactionRepository
mock object after thesaveTransaction
method was called. This ensures that no unintended interactions happened with theTransactionRepository
.
- The
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
Post a Comment
Leave Comment