Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Mockito | Testing Backend Applications
Spring Boot Backend
course content

Зміст курсу

Spring Boot Backend

Spring Boot Backend

1. Backend Development Basics
2. Spring Boot Basics
3. RESTful API
4. Working with Databases
5. Testing Backend Applications

book
Mockito

In the previous chapter, we recalled how to write unit tests. However, in our application, this isn't always convenient because testing a controller requires testing the service, and testing the service necessitates testing the repository.

This chain of dependencies raises the question: Do we need to test all modules and their dependencies, or can we simulate their behavior — in other words, mock them?

Mockito: Introduction

Mockito is a popular library for Java that is used to create mock objects in unit tests.

It allows developers to simulate the behavior of complex external dependencies (such as databases, services, or APIs) in order to isolate and test the logic of a specific component (usually a class or method) without interacting with these external systems.

This makes unit testing more predictable and faster since there is no need for real external resource calls during testing.

Mock Objects in Mockito

This allows developers to ensure that the component being tested (the unit) behaves correctly without relying on real dependencies such as databases or third-party services.

A class can be annotated with @Mock to create its mock version. This annotation is often used alongside @InjectMocks to automatically inject dependencies into the class under test.

This mock object can then be injected as a dependency into another object using the @InjectMocks annotation. This annotation automatically injects the mock objects into the class being tested.

Key Methods in Mockito

Mockito provides a wide range of methods for managing mock objects, each serving a specific purpose in the testing process. Below are the most important methods along with their explanations.

Testing Controllers with Mockito

Testing controllers often involves using mocks for services that are called within the controller. In this context, Mockito plays a key role in helping to isolate the controller’s logic from the service layer.

Quick Summary of the Video

We test controllers using the MockMvc class, but what does it offer and what are the benefits of using it?

With MockMvc, you can simulate various HTTP requests (GET, POST, PUT, etc.), pass parameters, headers, and verify responses, status codes, headers, and response bodies, making unit testing of controllers much easier.

For example, you can use methods like perform() to execute the request, andExpect() to verify the expected results, and content() to check the content of the response.

java

Test

copy
1234
mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk())

Additionally, you can chain assertions to check the HTTP status using status().isOk(), verify JSON response structures with jsonPath(), and more.

java

Test

copy
1234
.andExpect(jsonPath("$.id").value(bookResponseDTO.getId())) .andExpect(jsonPath("$.name").value(bookResponseDTO.getName())) .andExpect(jsonPath("$.author").value(bookResponseDTO.getAuthor())) .andExpect(jsonPath("$.price").value(bookResponseDTO.getPrice()));

These tools allow for comprehensive testing of the controller's behavior without actually making real HTTP calls.

Key Annotations

The @WebMvcTest(BookController.class) annotation is used for testing the BookController, loading only the components necessary for that specific controller. It excludes the rest of the Spring infrastructure, allowing you to focus on testing the controller itself.

Additionally, the @MockBean annotation applied to the bookService field creates a mock version of the service, allowing you to simulate its behavior. This helps isolate the controller from its dependencies and focus on testing its logic.

Tests for the updateBook Method

We wrote two tests for the updateBook method that cover all possible cases for this method. In the first test, we verify that everything was successful and that our entity was updated.

java

BookControllerTest

copy
1234567891011121314151617
@Test void testUpdateBook_whenBookExists_shouldReturnUpdatedBook() throws Exception { String bookId = "1"; when(bookService.updateBook(bookId, bookRequestDTO)).thenReturn(bookResponseDTO); mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(bookResponseDTO.getId())) .andExpect(jsonPath("$.name").value(bookResponseDTO.getName())) .andExpect(jsonPath("$.author").value(bookResponseDTO.getAuthor())) .andExpect(jsonPath("$.price").value(bookResponseDTO.getPrice())); verify(bookService).updateBook(bookId, bookRequestDTO); }

This test verifies the successful update of a book if it exists in the database. The updateBook() method of the service is called with the book's Id and a bookRequestDTO object, after which it returns a bookResponseDTO object representing the updated book.

Exception Handling Check

We also have a test where an exception occurs in the updateBook method, and we need to verify how the controller behaves in that situation.

java

BookControllerTest

copy
12345678910111213141516
@Test void testUpdateBook_whenBookNotFound_shouldReturnApiException() throws Exception { String bookId = "1"; String errorMessage = "ID not found"; when(bookService.updateBook(bookId, bookRequestDTO)) .thenThrow(new ApiException(errorMessage, HttpStatus.NOT_FOUND)); mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.error").value(errorMessage)); verify(bookService).updateBook(bookId, bookRequestDTO); }

This test checks the controller’s behavior when attempting to update a book that is not found in the database. The updateBook() method of the service is first called with the book's Id and a bookRequestDTO object.

However, instead of a successful result, the service throws an ApiException, indicating that the book with the given Id was not found.

Service Testing with Mockito

When testing a service, it’s important to isolate it from dependencies such as repositories or external APIs. Mockito allows you to create mocks for these dependencies and define their behavior in the tests.

Brief summary of the video

Let's start by testing our method when it successfully updates the data, meaning the Id is valid. Obviously, we'll use Mockito to mock the repository's behavior.

java

BookServiceTest

copy
123456789101112131415161718192021222324252627282930313233
@Test void testUpdateBook_whenBookExists_shouldUpdateAndReturnBook() { BookRequestDTO bookRequestDTO = new BookRequestDTO(); bookRequestDTO.setName("Updated Name"); bookRequestDTO.setAuthor("Updated Author"); bookRequestDTO.setPrice("150"); Book bookWithRepository = new Book(); bookWithRepository.setId("1"); bookWithRepository.setName("Original Name"); bookWithRepository.setAuthor("Original Author"); bookWithRepository.setPrice("100"); Book updateBook = new Book(); updateBook.setId("1"); updateBook.setName("Updated Name"); updateBook.setAuthor("Updated Author"); updateBook.setPrice("150"); when(bookRepository.findById("1")).thenReturn(Optional.of(bookWithRepository)); when(bookRepository.save(bookWithRepository)).thenReturn(updateBook); BookResponseDTO result = bookService.updateBook("1", bookRequestDTO); assertNotNull(result); assertEquals("1", result.getId()); assertEquals("Updated Name", result.getName()); assertEquals("Updated Author", result.getAuthor()); assertEquals("150", result.getPrice()); verify(bookRepository).findById("1"); verify(bookRepository).save(bookWithRepository); }

The method bookRepository.findById("1") is mocked to return an existing Book from the repository, and bookRepository.save(bookWithRepository) is mocked to return the updated book after saving the changes.

The updateBook() service method is called to update the book based on the bookRequestDTO, returning a BookResponseDTO.

assertNotNull(result) ensures the result isn't null, indicating a successful update, while assertEquals() checks that the ID, name, author, and price of the updated book match the expected values.

Finally, verify() ensures that findById() and save() in the repository were called with the correct parameters.

Checking an Exception in the Service

This test verifies that the service throws an ApiException if the Book to be updated is not found in the database.

java

BookServiceTest

copy
1234567891011121314151617181920
@Test void testUpdateBook_whenBookNotFound_shouldThrowApiException() { BookRequestDTO bookRequestDTO = new BookRequestDTO(); bookRequestDTO.setName("Updated Name"); bookRequestDTO.setAuthor("Updated Author"); bookRequestDTO.setPrice("150"); String idTest = "999"; when(bookRepository.findById(idTest)).thenReturn(Optional.empty()); ApiException apiException = assertThrows(ApiException.class, () -> { bookService.updateBook(idTest, bookRequestDTO); }); assertEquals("Not found book by id: " + idTest, apiException.getMessage()); assertEquals(HttpStatus.NOT_FOUND, apiException.getHttpStatus()); verify(bookRepository, never()).save(any(Book.class)); }

Initially, a BookRequestDTO object is created with the data for the update, and a test book Id that does not exist in the database "999" is assigned. The method bookRepository.findById(idTest) is mocked to return Optional.empty(), indicating that there is no book with that Id.

The test uses the assertThrows() method to check that when calling bookService.updateBook(idTest, bookRequestDTO), an ApiException is thrown.

Following this, the assertEquals() methods verify that the exception message matches the expected text and that the error status is equal to HttpStatus.NOT_FOUND.

The method verify(bookRepository, never()).save(any(Book.class)) ensures that the book saving method save() was not called, as the book was not found and the update was not performed.

Summary

Mockito is a library for creating mock objects that allows you to simulate the behavior of dependencies and isolate the code under test. This simplifies testing, making it faster, more stable, and independent of external resources.

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 5. Розділ 3
We're sorry to hear that something went wrong. What happened?
some-alt