Mockito: Simplifying Tests with Argument Matchers

Dana Prata
3 min readJun 13, 2024

--

1. Outline

  • Short Refresh: Mockito
  • Argument Matchers
  • Real Example
  • Takeaways

2. Short Refresh: Mockito

Mockito is a popular Java framework used for unit testing that allows developers to create mock objects to simulate the behaviour of real objects. This enables code testing in isolation by faking dependencies, defining their behaviour, and verifying interactions. This ensures that tests are focused, reliable, and do not rely on external systems like databases or network services.

3. Argument Matchers

Argument matchers are tools that help us specify general and flexible conditions/values for the arguments of a method call when we are writing tests. Instead of specifying the exact value an argument must have, we can specify conditions it should meet (e.g., any value, a specific type). Using matchers can make our tests more readable by focusing on the conditions that matter rather than on specific values.

Let’s say we have a method, someMethod(), and it takes a String argument like below:

Mockito.when(mockedObject.someMethod("example")).thenReturn(someValue);

Without using argument matchers, we need to specify the exact argument value that someMethod() is expected to be called with. This means we must know the exact argument in advance. If someMethod() is called with anything other than "example", the mock won't return someValue. This makes the test less flexible because it will only pass if the method is called with that exact argument.

If we make use of argument matchers the code will change like below:

Mockito.when(mockedObject.someMethod(anyString())).thenReturn(someValue);

Here, anyString() is an argument matcher that matches any string. In this case, the mock will return somevalue whenever someMethod is called.

Further, we can see a list of some of the most commonly used argument matchers in Mockito:

  • any() matches any object
  • any(Class<T> type) matches any object of the specified type
  • anyInt() matches any int value
  • anyString() matches any String value
  • eq(T value) matches if the argument equals the given value
  • isNull() matches if the argument is null
  • isNotNull() matches if the argument is not null
  • contains(String substring) matches if the argument contains the substring

4. Real Example

Let’s take an example to see how this works for real applications. We’ll make some unit tests for a Spring Boot app, written in Java and built with Gradle.

Prerequisites:

  • Project: Gradle
  • Language: Java 17
  • Framework: Spring Boot 3.3.0
  • Dependencies: Spring Web, Lombok, Mockito and JUnit

The app is called bookApp and it only has one controller named BookController.

package com.dep.bookApp.controllers;
import java.util.List;
import com.dep.bookApp.models.Book;
import com.dep.bookApp.services.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/books")
public class BookController {
private final BookService bookService;

@Autowired
public BookController(BookService bookService) {
this.bookService = bookService;
}

@GetMapping("/")
public ResponseEntity<List<Book>> findBooks(@RequestParam String author, @RequestParam String genre) {
List<Book> books = bookService.findBooks(author, genre);

if (books.isEmpty()) {
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.ok(books);
}
}
}

We can see that it exposes a GET resource that finds books by author and genre.

OK, now let’s take a look at the BookControllerTest.

package com.dep.bookApp.controllers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import com.dep.bookApp.models.Book;
import com.dep.bookApp.services.BookService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.ResponseEntity;

@ExtendWith(MockitoExtension.class)
public class BookControllerTest {
@InjectMocks
private BookController bookController;

@Mock
private BookService bookService;

@Test
public void findBooks_ReturnsNoContent() {
when(bookService.findBooks(anyString(), anyString())).thenReturn( Collections.emptyList());

ResponseEntity<List<Book>> response1 = bookController.findBooks("Author1", "Comedy");
ResponseEntity<List<Book>> response2 = bookController.findBooks("Author2", "Drama");

assertEquals(204, response1.getStatusCode().value());
assertEquals(204, response2.getStatusCode().value());
}

@Test
public void findBooks_ReturnsListOfBooks() {
Book book = new Book();
book.setTitle("Title");
book.setAuthor("Author");
book.setGenre("Comedy");

when(bookService.findBooks( anyString(), anyString())).thenReturn( List.of( book ) );

ResponseEntity<List<Book>> response1 = bookController.findBooks( "Author", "Comedy");

assertEquals(200, response1.getStatusCode().value());
assertEquals(1, response1.getBody().size());
assertEquals("Title", response1.getBody().get(0).getTitle());
assertEquals("Author", response1.getBody().get(0).getAuthor());
assertEquals("Comedy", response1.getBody().get(0).getGenre());

ResponseEntity<List<Book>> response2 = bookController.findBooks( "Test", "Drama");

assertEquals(200, response2.getStatusCode().value());
assertEquals(1, response2.getBody().size());
assertEquals("Title", response2.getBody().get(0).getTitle());
assertEquals("Author", response2.getBody().get(0).getAuthor());
assertEquals("Comedy", response2.getBody().get(0).getGenre());
}
}

As we can see, it doesn’t matter with which String values we call findBook() method, it will always return an empty list for the first unit test or the book object for the second one. More than this, we don’t have to set up multiple when statements for different combinations of author and genre. One when statement covers all possible combinations.

5. Takeaways

  • Flexibility: Allows general conditions for arguments, making tests more adaptable
  • Simplified Setup: Reduces the number of when statements needed, easing test setup
  • Readability: Makes test intent clearer with expressive matchers
  • Dynamic Data: Handles unpredictable inputs robustly
  • Reduced Redundancy: Avoids repetitive stubs for different argument values

You can find the entire code in my repository on GitLab.

--

--

Dana Prata

👩‍💻Senior Software Engineer | 🎉Tech Enthusiast