Introduction
BDDMockito.willAnswer()
is a method provided by Mockito to support the Behavior-Driven Development (BDD) style of writing tests. It is used to specify custom behavior for a method call on a mock object. This is particularly useful when you need to perform more complex operations or handle specific cases that cannot be easily managed with willReturn()
or willThrow()
. This tutorial will demonstrate how to use BDDMockito.willAnswer()
to mock custom behavior 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 LibraryService
class that has a dependency on a BookRepository
. Our goal is to test the LibraryService
methods using BDDMockito.willAnswer()
to handle custom behavior.
LibraryService and BookRepository Classes
First, create the Book
class, the BookRepository
interface, and the LibraryService
class.
public class Book {
private String title;
private String author;
// Constructor, getters, and setters
public Book(String title, String author) {
this.title = title;
this.author = author;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
public interface BookRepository {
void saveBook(Book book);
Book findBookByTitle(String title);
List<Book> findAllBooks();
}
public class LibraryService {
private final BookRepository bookRepository;
public LibraryService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
public void addBook(String title, String author) {
Book book = new Book(title, author);
bookRepository.saveBook(book);
}
public Book getBookByTitle(String title) {
return bookRepository.findBookByTitle(title);
}
public List<Book> getAllBooks() {
return bookRepository.findAllBooks();
}
}
JUnit 5 Test Class with BDDMockito
Create a test class for LibraryService
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;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Arrays;
import java.util.List;
@ExtendWith(MockitoExtension.class)
public class LibraryServiceTest {
@Mock
private BookRepository bookRepository;
@InjectMocks
private LibraryService libraryService;
@Test
public void testAddBook() {
// Given
String title = "Mockito in Action";
String author = "Ramesh Fadatare";
willAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
Book book = invocation.getArgument(0);
assertEquals("Mockito in Action", book.getTitle());
assertEquals("Ramesh Fadatare", book.getAuthor());
return null;
}
}).given(bookRepository).saveBook(any(Book.class));
// When
libraryService.addBook(title, author);
// Then
then(bookRepository).should().saveBook(any(Book.class));
}
@Test
public void testGetBookByTitle() {
// Given
String title = "Mockito in Action";
Book book = new Book(title, "Ramesh Fadatare");
willAnswer(invocation -> {
String arg = invocation.getArgument(0);
return "Mockito in Action".equals(arg) ? book : null;
}).given(bookRepository).findBookByTitle(anyString());
// When
Book result = libraryService.getBookByTitle(title);
// Then
assertNotNull(result);
assertEquals(title, result.getTitle());
assertEquals("Ramesh Fadatare", result.getAuthor());
}
@Test
public void testGetAllBooks() {
// Given
Book book1 = new Book("Mockito in Action", "Ramesh Fadatare");
Book book2 = new Book("Effective Java", "Joshua Bloch");
List<Book> books = Arrays.asList(book1, book2);
willAnswer(invocation -> books).given(bookRepository).findAllBooks();
// When
List<Book> result = libraryService.getAllBooks();
// Then
assertNotNull(result);
assertEquals(2, result.size());
assertEquals("Mockito in Action", result.get(0).getTitle());
assertEquals("Effective Java", result.get(1).getTitle());
}
}
Explanation
Creating Mocks with
@Mock
:- The
@Mock
annotation creates a mock instance of theBookRepository
interface. This mock instance can be used to simulate the behavior of theBookRepository
in a controlled way.
- The
Injecting Mocks with
@InjectMocks
:- The
@InjectMocks
annotation injects the mockBookRepository
into theLibraryService
instance to provide a controlled test environment. This allows theLibraryService
methods to be tested in isolation from the actualBookRepository
implementation.
- The
Using BDDMockito:
willAnswer()
: ThewillAnswer(new Answer<Void>() { ... }).given(bookRepository).saveBook(any(Book.class));
method configures the mockBookRepository
to execute custom behavior when thesaveBook
method is called with anyBook
object.- Inside the
answer
method, we retrieve the arguments passed to thesaveBook
method and perform assertions to verify the book's title and author. This allows us to check that theaddBook
method of theLibraryService
class correctly interacts with theBookRepository
. - Similarly, the
willAnswer(invocation -> { ... }).given(bookRepository).findBookByTitle(anyString());
method configures the mockBookRepository
to return a specificBook
object based on the argument passed to thefindBookByTitle
method.
Assertions:
- The
assertNotNull(result);
andassertEquals(...);
methods verify that thegetBookByTitle
andgetAllBooks
methods return the expected results when the mocked methods are called.
- The
Additional Scenarios
Scenario: Handling Multiple Return Values with BDDMockito
In this scenario, we will demonstrate how to handle multiple return values using BDDMockito.willAnswer()
.
@Test
public void testGetBookByTitleWithMultipleReturnValues() {
// Given
Book book1 = new Book("Mockito in Action", "Ramesh Fadatare");
Book book2 = new Book("Effective Java", "Joshua Bloch");
willAnswer(invocation -> {
String title = invocation.getArgument(0);
if ("Mockito in Action".equals(title)) {
return book1;
} else if ("Effective Java".equals(title)) {
return book2;
}
return null;
}).given(bookRepository).findBookByTitle(anyString());
// When
Book result1 = libraryService.getBookByTitle("Mockito in Action");
Book result2 = libraryService.getBookByTitle("Effective Java");
// Then
assertNotNull(result1);
assertEquals("Mockito in Action", result1.getTitle());
assertEquals("Ramesh Fadatare", result1.getAuthor());
assertNotNull(result2);
assertEquals("Effective Java", result2.getTitle());
assertEquals("Joshua Bloch", result2.getAuthor());
}
Explanation
- Handling Multiple Return Values with BDDMockito:
- The
willAnswer(invocation -> { ... }).given(bookRepository).findBookByTitle(anyString());
method configures the mockBookRepository
to return differentBook
objects based on the argument passed to thefindBookByTitle
method. This allows thegetBookByTitle
method of theLibraryService
class to be tested with multiple return values.
- The
Scenario: Handling Exceptions with BDDMockito
In this scenario, we will demonstrate how to handle exceptions using BDDMockito.willAnswer()
.
@Test
public void testGetBookByTitleThrowsException() {
// Given
String title = "Mockito in Action";
willAnswer(invocation -> {
throw new RuntimeException("Book not found");
}).given(bookRepository).findBookByTitle
(title);
// When & Then
RuntimeException exception = assertThrows(RuntimeException.class, () -> {
libraryService.getBookByTitle(title);
});
assertEquals("Book not found", exception.getMessage());
}
Explanation
- Handling Exceptions with BDDMockito:
- The
willAnswer(invocation -> { throw new RuntimeException("Book not found"); }).given(bookRepository).findBookByTitle(title);
method configures the mockBookRepository
to throw aRuntimeException
when thefindBookByTitle
method is called with the specified title. This allows thegetBookByTitle
method of theLibraryService
class to be tested with controlled exception handling behavior.
- The
Conclusion
Using BDDMockito.willAnswer()
in Mockito allows you to write more readable and expressive tests that follow the Behavior-Driven Development (BDD) style. By using willAnswer()
for stubbing methods with custom behavior, you can handle various scenarios and control the behavior of mock objects. This step-by-step guide demonstrated how to effectively use BDDMockito.willAnswer()
in your unit tests, covering different scenarios to ensure comprehensive testing of the LibraryService
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