Mockito BDDMockito willAnswer()

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

  1. Creating Mocks with @Mock:

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

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

    • willAnswer(): The willAnswer(new Answer<Void>() { ... }).given(bookRepository).saveBook(any(Book.class)); method configures the mock BookRepository to execute custom behavior when the saveBook method is called with any Book object.
    • Inside the answer method, we retrieve the arguments passed to the saveBook method and perform assertions to verify the book's title and author. This allows us to check that the addBook method of the LibraryService class correctly interacts with the BookRepository.
    • Similarly, the willAnswer(invocation -> { ... }).given(bookRepository).findBookByTitle(anyString()); method configures the mock BookRepository to return a specific Book object based on the argument passed to the findBookByTitle method.
  4. Assertions:

    • The assertNotNull(result); and assertEquals(...); methods verify that the getBookByTitle and getAllBooks methods return the expected results when the mocked methods are called.

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

  1. Handling Multiple Return Values with BDDMockito:
    • The willAnswer(invocation -> { ... }).given(bookRepository).findBookByTitle(anyString()); method configures the mock BookRepository to return different Book objects based on the argument passed to the findBookByTitle method. This allows the getBookByTitle method of the LibraryService class to be tested with multiple return values.

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

  1. Handling Exceptions with BDDMockito:
    • The willAnswer(invocation -> { throw new RuntimeException("Book not found"); }).given(bookRepository).findBookByTitle(title); method configures the mock BookRepository to throw a RuntimeException when the findBookByTitle method is called with the specified title. This allows the getBookByTitle method of the LibraryService class to be tested with controlled exception handling behavior.

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