1. Overview
In this tutorial, we’ll get familiar with Mockito’s AdditionalAnswers class and its methods.
2. Returning Arguments
The main purpose of the AdditionalAnswers class is to return parameters passed to a mocked method.
For example, when updating an object, the method being mocked usually just returns the updated object. Using the methods from AdditionalAnswers, we can instead return a specific parameter passed as an argument to the method, based on its position in the parameter list.
Furthermore, AdditionalAnswers has different implementations of the Answer class.
To begin our demonstration, let’s create a library project.
Firstly, we’ll create one simple model:
public class Book {
private Long bookId;
private String title;
private String author;
private int numberOfPages;
// constructors, getters and setters
}
Additionally, we need a repository class for book retrieval:
public class BookRepository {
public Book getByBookId(Long bookId) {
return new Book(bookId, "To Kill a Mocking Bird", "Harper Lee", 256);
}
public Book save(Book book) {
return new Book(book.getBookId(), book.getTitle(), book.getAuthor(), book.getNumberOfPages());
}
public Book selectRandomBook(Book bookOne, Book bookTwo, Book bookThree) {
List<Book> selection = new ArrayList<>();
selection.add(bookOne);
selection.add(bookTwo);
selection.add(bookThree);
Random random = new Random();
return selection.get(random.nextInt(selection.size()));
}
}
Correspondingly, we have a service class that invokes our repository methods:
public class BookService {
private final BookRepository bookRepository;
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
public Book getByBookId(Long id) {
return bookRepository.getByBookId(id);
}
public Book save(Book book) {
return bookRepository.save(book);
}
public Book selectRandomBook(Book book1, Book book2, Book book3) {
return bookRepository.selectRandomBook(book1, book2, book3);
}
}
With this in mind, let’s create some tests.
2.1. Returning the First Argument
For our test class, we need to enable the use of annotations with Mockito tests by annotating the JUnit test class to run with MockitoJUnitRunner. Furthermore, we’ll need to mock our service and repository class:
@RunWith(MockitoJUnitRunner.class)
public class BookServiceUnitTest {
@InjectMocks
private BookService bookService;
@Mock
private BookRepository bookRepository;
// test methods
}
Firstly, lets create a test returning the first argument – AdditionalAnswers.returnsFirstArg():
@Test
public void givenSaveMethodMocked_whenSaveInvoked_ThenReturnFirstArgument_UnitTest() {
Book book = new Book("To Kill a Mocking Bird", "Harper Lee", 256);
Mockito.when(bookRepository.save(any(Book.class))).then(AdditionalAnswers.returnsFirstArg());
Book savedBook = bookService.save(book);
assertEquals(savedBook, book);
}
In other words, we’ll mock the save method from our BookRepository class, which accepts the Book object.
When we run this test, it will indeed return the first argument, which is equal to the Book object we saved.
2.2. Returning the Second Argument
Secondly, we create a test using AdditionalAnswers.returnsSecondArg():
@Test
public void givenCheckifEqualsMethodMocked_whenCheckifEqualsInvoked_ThenReturnSecondArgument_UnitTest() {
Book book1 = new Book(1L, "The Stranger", "Albert Camus", 456);
Book book2 = new Book(2L, "Animal Farm", "George Orwell", 300);
Book book3 = new Book(3L, "Romeo and Juliet", "William Shakespeare", 200);
Mockito.when(bookRepository.selectRandomBook(any(Book.class), any(Book.class),
any(Book.class))).then(AdditionalAnswers.returnsSecondArg());
Book secondBook = bookService.selectRandomBook(book1, book2, book3);
assertEquals(secondBook, book2);
}
In this case, when our selectRandomBook method executes, the method will return the second book.
2.3. Returning the Last Argument
Similarly, we can use AdditionalAnswers.returnsLastArg() to get the last argument that we passed to our method:
@Test
public void givenCheckifEqualsMethodMocked_whenCheckifEqualsInvoked_ThenReturnLastArgument_UnitTest() {
Book book1 = new Book(1L, "The Stranger", "Albert Camus", 456);
Book book2 = new Book(2L, "Animal Farm", "George Orwell", 300);
Book book3 = new Book(3L, "Romeo and Juliet", "William Shakespeare", 200);
Mockito.when(bookRepository.selectRandomBook(any(Book.class), any(Book.class),
any(Book.class))).then(AdditionalAnswers.returnsLastArg());
Book lastBook = bookService.selectRandomBook(book1, book2, book3);
assertEquals(lastBook, book3);
}
Here, the method invoked will return the third book, as it is the last parameter.
2.4. Returning the Argument at Index
Finally, let’s write a test using the method that enables us to return an argument at a given index – AdditionalAnswers.returnsArgAt(int index):
@Test
public void givenCheckifEqualsMethodMocked_whenCheckifEqualsInvoked_ThenReturnArgumentAtIndex_UnitTest() {
Book book1 = new Book(1L, "The Stranger", "Albert Camus", 456);
Book book2 = new Book(2L, "Animal Farm", "George Orwell", 300);
Book book3 = new Book(3L, "Romeo and Juliet", "William Shakespeare", 200);
Mockito.when(bookRepository.selectRandomBook(any(Book.class), any(Book.class),
any(Book.class))).then(AdditionalAnswers.returnsArgAt(1));
Book bookOnIndex = bookService.selectRandomBook(book1, book2, book3);
assertEquals(bookOnIndex, book2);
}
In the end, since we asked for the argument from index 1, we’ll get the second argument — specifically, book2 in this case.
3. Creating an Answer From a Functional Interface
AdditionalAnswers offers a concise and neater way to create answers from functional interfaces. To do so, it provides two convenient methods: answer() and answerVoid().
So, let’s go down the rabbit hole and see how to use them in practice.
Please bear in mind that these two methods are annotated with @Incubating. This means that they might change at a later time based on the community feedback.
3.1. Using AdditionalAnswers.answer()
This method is mainly introduced to create a strongly typed answer in Java 8 using a functional interface.
Typically, Mockito comes with a set of ready-to-use generic interfaces that we can use to configure a mock’s answer. For example, it provides Answer1<T,A0> for a single argument invocation.
Now, let’s illustrate how to use the AdditionalAnswers.answer() to create an answer that returns a Book object:
@Test
public void givenMockedMethod_whenMethodInvoked_thenReturnBook() {
Long id = 1L;
when(bookRepository.getByBookId(anyLong())).thenAnswer(answer(BookServiceUnitTest::buildBook));
assertNotNull(bookService.getByBookId(id));
assertEquals("The Stranger", bookService.getByBookId(id).getTitle());
}
private static Book buildBook(Long bookId) {
return new Book(bookId, "The Stranger", "Albert Camus", 456);
}
As shown above, we used a method reference to denote the Answer1 interface.
3.2. Using AdditionalAnswers.answerVoid()
Similarly, we can use answerVoid() to configure a mock’s answer for arguments invocation that returns nothing.
Next, let’s exemplify the use of the AdditionalAnswers.answerVoid() method using a test case:
@Test
public void givenMockedMethod_whenMethodInvoked_thenReturnVoid() {
Long id = 2L;
when(bookRepository.getByBookId(anyLong())).thenAnswer(answerVoid(BookServiceUnitTest::printBookId));
bookService.getByBookId(id);
verify(bookRepository, times(1)).getByBookId(id);
}
private static void printBookId(Long bookId) {
System.out.println(bookId);
}
As we can see, we used the VoidAnswer1
The answer method specifies an action that is executed when we interact with the mock. In our case, we simply print the passed book’s id.
4. Conclusion
Altogether, this tutorial has covered the methods of Mockito’s AdditionalAnswers class.
The implementation of these examples and code snippets are available over on GitHub.