avatarIvan Franchin

Summary

The provided content is a comprehensive guide on implementing unit tests for a reactive Book API application using Spring Boot, Spring WebFlux, and MongoDB.

Abstract

The article details the process of creating unit tests for a reactive Book API application built with Spring Boot and Spring WebFlux. It covers the creation of test packages, disabling default application tests, and writing test cases for mapper, service, and controller layers, as well as DTO classes. The guide emphasizes the use of Spring's testing utilities and annotations to ensure that individual components of the application function correctly in isolation. It also demonstrates how to use StepVerifier for testing reactive streams and JacksonTester for JSON serialization and deserialization. The article concludes with instructions on running the tests and encourages reader engagement through claps, shares, and following the author on social media.

Opinions

  • The author believes in the importance of unit testing to ensure the reliability of individual components within a software application.
  • The use of Spring's testing framework is advocated for its ability to facilitate the testing process with features like dependency injection and mocking.
  • The author values the practice of testing both the happy path (e.g., when resources exist) and edge cases (e.g., when resources do not exist) to ensure robust test coverage.
  • The author encourages community engagement and feedback, suggesting that reader interaction is valuable for the dissemination and improvement of their content.
  • The author is confident in the effectiveness of the provided unit testing strategies, as evidenced by the expectation that all tests should pass when executed.

Implementing Unit Tests for a Reactive App that uses Spring WebFlux and MongoDB

Step-by-step guide on how to implement Unit tests for Book API using Spring Testing Library

Photo by Dorrell Tibbs on Unsplash

In this article, we will explain how to implement Unit Tests in a Spring Boot Reactive application, whose name is Book API.

You can find the complete code and implementation in the article linked below. Feel free to follow the steps explained in the article and get started.

Unit test is a software testing approach that focuses on isolating and evaluating individual components or units of a software application in isolation.

In the context of the Book API, unit test would involve testing each component, class, or method separately to ensure that they perform their specific functions correctly.

Besides, we will explore the annotations and utilities that Spring Boot provides in order to unit test the applications.

So, let’s get started!

Create some packages

In the src/test/java folder, let’s create the following packages inside the com.example.bookapi root package: controller, mapper, and service.

Disable the BookApiApplicationTests class

Let’s disable the BookApiApplicationTests class that was generated while creating the project using Spring Initializr. We will not implement test cases in it. For it, add the following bold lines:

package com.example.bookapi;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@Disabled
@SpringBootTest
class BookApiApplicationTests {

    @Test
    void contextLoads() {
    }

}

Create the BookMapperImplTest class

In the mapper package, create the BookMapperImplTest class with the following content:

package com.example.bookapi.mapper;

import com.example.bookapi.controller.dto.BookResponse;
import com.example.bookapi.controller.dto.CreateBookRequest;
import com.example.bookapi.controller.dto.UpdateBookRequest;
import com.example.bookapi.model.Book;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(SpringExtension.class)
@Import(BookMapperImpl.class)
class BookMapperImplTest {

    @Autowired
    private BookMapper bookMapper;

    @Test
    void testToBook() {
        CreateBookRequest createBookRequest = new CreateBookRequest("title", "author", 2023);

        Book book = bookMapper.toBook(createBookRequest);

        assertThat(book.getId()).isNull();
        assertThat(book.getTitle()).isEqualTo("title");
        assertThat(book.getAuthor()).isEqualTo("author");
        assertThat(book.getYear()).isEqualTo(2023);
    }

    @ParameterizedTest
    @MethodSource("provideUpdateBookRequests")
    void testUpdateBookFromUpdateBookRequest(UpdateBookRequest updateupdateBookRequest, Book expectedBook) {
        Book book = new Book("title", "author",2023);

        bookMapper.updateBookFromUpdateBookRequest(updateupdateBookRequest, book);

        assertThat(book.getId()).isEqualTo(expectedBook.getId());
        assertThat(book.getTitle()).isEqualTo(expectedBook.getTitle());
        assertThat(book.getAuthor()).isEqualTo(expectedBook.getAuthor());
        assertThat(book.getYear()).isEqualTo(expectedBook.getYear());
    }

    private static Stream<Arguments> provideUpdateBookRequests() {
        return Stream.of(
                Arguments.of(new UpdateBookRequest("newTitle", null, null), new Book("newTitle", "author", 2023)),
                Arguments.of(new UpdateBookRequest(null, "newAuthor", null), new Book("title", "newAuthor", 2023)),
                Arguments.of(new UpdateBookRequest(null, null, 2024), new Book("title", "author", 2024)),
                Arguments.of(new UpdateBookRequest("newTitle", "newAuthor", 2024), new Book("newTitle", "newAuthor", 2024))
        );
    }

    @Test
    void testToBookResponse() {
        Book book = new Book("title", "author", 2023);

        BookResponse bookResponse = bookMapper.toBookResponse(book);

        assertThat(bookResponse.id()).isNull();
        assertThat(bookResponse.title()).isEqualTo("title");
        assertThat(bookResponse.author()).isEqualTo("author");
        assertThat(bookResponse.year()).isEqualTo(2023);
    }
}

This BookMapperImplTest is designed to test the functionality of the BookMapperImpl class, which is responsible for mapping between different representations of a book (DTOs and the entity).

Let's break down the key components of this test class:

Annotations

  • @ExtendWith(SpringExtension.class): This annotation integrates the Spring TestContext Framework with JUnit 5, allowing the usage of the Spring test features, such as dependency injection of beans;
  • @Import(BookMapperImpl.class): Imports the BookMapperImpl class, indicating that the test will focus on the functionality provided by this specific mapper implementation.

Fields

  • @Autowired BookMapper bookMapper: Injects an instance of the BookMapperImpl class to test its functionality.

Test Methods

  • testToBook(): Tests the toBook method, which converts a CreateBookRequest DTO to a Book entity. It checks that the resulting Book has the expected attributes;
  • testUpdateBookFromUpdateBookRequest(): Parameterized test for the updateBookFromUpdateBookRequest method. It tests the method's ability to update a Book entity based on different UpdateBookRequest DTOs. The provided arguments include the update request and the expected resulting Book;
  • testToBookResponse(): Tests the toBookResponse method, which converts a Book entity to a BookResponse DTO. It checks that the resulting BookResponse has the expected attributes.

Create the BookServiceImplTest class

In the service package, create the BookServiceImplTest class with the content below:

package com.example.bookapi.service;

import com.example.bookapi.exception.BookNotFoundException;
import com.example.bookapi.model.Book;
import com.example.bookapi.repository.BookRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

@ExtendWith(SpringExtension.class)
@Import(BookServiceImpl.class)
class BookServiceImplTest {

    @Autowired
    private BookService bookService;

    @MockBean
    private BookRepository bookRepository;

    @Test
    void testGetBooksWhenThereIsNoBook() {
        when(bookRepository.findAll()).thenReturn(Flux.empty());

        StepVerifier.create(bookService.getBooks())
                .expectNextCount(0)
                .verifyComplete();
    }

    @Test
    void testGetBooksWhenThereIsOneBook() {
        Book book = getDefaultBook();
        when(bookRepository.findAll()).thenReturn(Flux.just(book));

        StepVerifier.create(bookService.getBooks())
                .expectNext(book)
                .verifyComplete();
    }

    @Test
    void testValidateAndGetBookByIdWhenExisting() {
        Book book = getDefaultBook();
        when(bookRepository.findById(anyString())).thenReturn(Mono.just(book));

        StepVerifier.create(bookService.validateAndGetBookById("123"))
                .consumeNextWith(bookFound -> assertThat(bookFound).isEqualTo(book))
                .verifyComplete();
    }

    @Test
    void testValidateAndGetBookByIdWhenNonExisting() {
        when(bookRepository.findById(anyString())).thenReturn(Mono.empty());

        StepVerifier.create(bookService.validateAndGetBookById("123"))
                .verifyErrorMatches(ex -> ex instanceof BookNotFoundException);
    }

    @Test
    void testSaveBook() {
        Book book = getDefaultBook();
        when(bookRepository.save(any(Book.class))).thenReturn(Mono.just(book));

        StepVerifier.create(bookService.saveBook(book))
                .consumeNextWith(bookFound -> assertThat(bookFound).isEqualTo(book))
                .verifyComplete();
    }

    @Test
    void testDeleteBook() {
        Book book = getDefaultBook();
        when(bookRepository.delete(any(Book.class))).thenReturn(Mono.empty());

        StepVerifier.create(bookService.deleteBook(book))
                .verifyComplete();
    }

    private Book getDefaultBook() {
        return new Book("123", "title", "author", 2023);
    }
}

The BookServiceImplTest is designed to test the functionality of the BookServiceImpl class, which is the implementation of the BookService interface. For each test, it uses StepVerifier to verify the behavior of the reactive streams (Flux and Mono) returned by the service methods. This includes assertions about the emitted values, completion, and potential errors

Let's break down the key components of this test class:

Annotations

  • @Import(BookServiceImpl.class): Imports the BookServiceImpl class, indicating that the test will focus on the functionality provided by this specific service implementation.

Fields

  • @Autowired BookService bookService: Injects an instance of the BookServiceImpl class to test its functionality;
  • @MockBean BookRepository bookRepository: Injects a mock instance of the BookRepository to simulate database interactions during testing.

Test Methods

  • testGetBooksWhenThereIsNoBook(): Tests the getBooks method of the BookService when there are no books in the repository. It uses StepVerifier to assert that the resulting Flux is empty;
  • testGetBooksWhenThereIsOneBook(): Tests the getBooks method when there is one book in the repository. It verifies that the Flux emitted the book;
  • testValidateAndGetBookByIdWhenExisting(): Tests the validateAndGetBookById method when a book with a specified ID exists. It checks that the resulting Mono emits the expected book;
  • testValidateAndGetBookByIdWhenNonExisting(): Tests the validateAndGetBookById method when a book with a specified ID does not exist. It checks that the resulting Mono emits a BookNotFoundException;
  • testSaveBook(): Tests the saveBook method by verifying that the resulting Mono emits the saved book;
  • testDeleteBook(): Tests the deleteBook method by verifying that the resulting Mono completes successfully.

Create the DTO test classes

In the controller package, let’s create a new package called dto. Inside the dto package, we will create the three test classes: CreateBookRequestTest, UpdateBookRequestTest and BookResponseTest:

Let’s start creating the CreateBookRequestTest class with the following content:

package com.example.bookapi.controller.dto;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.boot.test.json.JsonContent;

import java.io.IOException;

import static org.assertj.core.api.Assertions.assertThat;

@JsonTest
class CreateBookRequestTest {

    @Autowired
    private JacksonTester<CreateBookRequest> jacksonTester;

    @Test
    void testSerialize() throws IOException {
        CreateBookRequest createBookRequest = new CreateBookRequest("title", "author", 2023);

        JsonContent<CreateBookRequest> jsonContent = jacksonTester.write(createBookRequest);

        assertThat(jsonContent)
                .hasJsonPathStringValue("@.title")
                .extractingJsonPathStringValue("@.title").isEqualTo("title");

        assertThat(jsonContent)
                .hasJsonPathStringValue("@.author")
                .extractingJsonPathStringValue("@.author").isEqualTo("author");

        assertThat(jsonContent)
                .hasJsonPathNumberValue("@.year")
                .extractingJsonPathNumberValue("@.year").isEqualTo(2023);
    }

    @Test
    void testDeserialize() throws IOException {
        String content = "{\"title\":\"title\",\"author\":\"author\",\"year\":2023}";

        CreateBookRequest createBookRequest = jacksonTester.parseObject(content);

        assertThat(createBookRequest.title()).isEqualTo("title");
        assertThat(createBookRequest.year()).isEqualTo(2023);
        assertThat(createBookRequest.author()).isEqualTo("author");
    }
}

Next, we will create the UpdateBookRequestTest class:

package com.example.bookapi.controller.dto;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.boot.test.json.JsonContent;

import java.io.IOException;

import static org.assertj.core.api.Assertions.assertThat;

@JsonTest
class UpdateBookRequestTest {

    @Autowired
    private JacksonTester<UpdateBookRequest> jacksonTester;

    @Test
    void testSerialize() throws IOException {
        UpdateBookRequest updateBookRequest = new UpdateBookRequest("title", "author", 2023);

        JsonContent<UpdateBookRequest> jsonContent = jacksonTester.write(updateBookRequest);

        assertThat(jsonContent)
                .hasJsonPathStringValue("@.title")
                .extractingJsonPathStringValue("@.title").isEqualTo("title");

        assertThat(jsonContent)
                .hasJsonPathStringValue("@.author")
                .extractingJsonPathStringValue("@.author").isEqualTo("author");

        assertThat(jsonContent)
                .hasJsonPathNumberValue("@.year")
                .extractingJsonPathNumberValue("@.year").isEqualTo(2023);

    }

    @Test
    void testDeserialize() throws IOException {
        String content = "{\"title\":\"title\",\"author\":\"author\",\"year\":2023}";

        UpdateBookRequest updateBookRequest = jacksonTester.parseObject(content);

        assertThat(updateBookRequest.title()).isEqualTo("title");
        assertThat(updateBookRequest.year()).isEqualTo(2023);
        assertThat(updateBookRequest.author()).isEqualTo("author");
    }
}

Finally, let’s create the BookResponseTest class with the content below:

package com.example.bookapi.controller.dto;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.boot.test.json.JsonContent;

import java.io.IOException;

import static org.assertj.core.api.Assertions.assertThat;

@JsonTest
class BookResponseTest {

    @Autowired
    private JacksonTester<BookResponse> jacksonTester;

    @Test
    void testSerialize() throws IOException {
        BookResponse bookResponse = new BookResponse("123", "title", "author", 2023);

        JsonContent<BookResponse> jsonContent = jacksonTester.write(bookResponse);

        assertThat(jsonContent)
                .hasJsonPathStringValue("@.id")
                .extractingJsonPathStringValue("@.id").isEqualTo("123");

        assertThat(jsonContent)
                .hasJsonPathStringValue("@.title")
                .extractingJsonPathStringValue("@.title").isEqualTo("title");

        assertThat(jsonContent)
                .hasJsonPathStringValue("@.author")
                .extractingJsonPathStringValue("@.author").isEqualTo("author");

        assertThat(jsonContent)
                .hasJsonPathNumberValue("@.year")
                .extractingJsonPathNumberValue("@.year").isEqualTo(2023);
    }

    @Test
    void testDeserialize() throws IOException {
        String content = "{\"id\":\"123\",\"title\":\"title\",\"author\":\"author\",\"year\":2023}";

        BookResponse bookResponse = jacksonTester.parseObject(content);

        assertThat(bookResponse.id()).isEqualTo("123");
        assertThat(bookResponse.title()).isEqualTo("title");
        assertThat(bookResponse.author()).isEqualTo("author");
        assertThat(bookResponse.year()).isEqualTo(2023);
    }
}

These test classes are employed to verify the correct serialization and deserialization of Data Transfer Object (DTO) classes using Jackson.

The @JsonTest annotation signals that this class is designed for JSON testing, establishing a Spring test environment that incorporates automatic Jackson configuration for JSON serialization and deserialization.

Additionally, the JacksonTester utility, supplied by Spring Boot, facilitates JSON serialization and deserialization testing.

Create the BookControllerTest class

In the controller package, create the BookControllerTest class with the content below:

package com.example.bookapi.controller;

import com.example.bookapi.controller.dto.BookResponse;
import com.example.bookapi.controller.dto.CreateBookRequest;
import com.example.bookapi.controller.dto.UpdateBookRequest;
import com.example.bookapi.exception.BookNotFoundException;
import com.example.bookapi.mapper.BookMapperImpl;
import com.example.bookapi.model.Book;
import com.example.bookapi.service.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

@WebFluxTest(BookController.class)
@Import(BookMapperImpl.class)
class BookControllerTest {

    @Autowired
    private WebTestClient webTestClient;

    @MockBean
    private BookService bookService;

    @Test
    void testGetBooksWhenThereIsNone() {
        when(bookService.getBooks()).thenReturn(Flux.empty());

        webTestClient.get()
                .uri(API_BOOKS_URL)
                .exchange()
                .expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_NDJSON_VALUE)
                .expectBodyList(BookResponse.class)
                .hasSize(0);
    }

    @Test
    void testGetBooksWhenThereIsOne() {
        Book book = getDefaultBook();
        when(bookService.getBooks()).thenReturn(Flux.just(book));

        webTestClient.get()
                .uri(API_BOOKS_URL)
                .exchange()
                .expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_NDJSON_VALUE)
                .expectBodyList(BookResponse.class)
                .consumeWith(response -> {
                    assertThat(response.getResponseBody()).isNotNull();
                    assertThat(response.getResponseBody().get(0).id()).isEqualTo(book.getId());
                    assertThat(response.getResponseBody().get(0).title()).isEqualTo(book.getTitle());
                    assertThat(response.getResponseBody().get(0).author()).isEqualTo(book.getAuthor());
                    assertThat(response.getResponseBody().get(0).year()).isEqualTo(book.getYear());
                });
    }

    @Test
    void testGetBookByImdbIdWhenNonExistent() {
        when(bookService.validateAndGetBookById(anyString())).thenReturn(Mono.error(new BookNotFoundException("123")));

        webTestClient.get()
                .uri(API_BOOKS_ID_URL.formatted("123"))
                .exchange()
                .expectStatus().isNotFound()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBody(BookResponse.class);
    }

    @Test
    void testGetBookByImdbIdWhenExistent() {
        Book book = getDefaultBook();
        when(bookService.validateAndGetBookById(anyString())).thenReturn(Mono.just(book));

        webTestClient.get()
                .uri(API_BOOKS_ID_URL.formatted("123"))
                .exchange()
                .expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBody(BookResponse.class)
                .consumeWith(response -> {
                    assertThat(response.getResponseBody()).isNotNull();
                    assertThat(response.getResponseBody().id()).isEqualTo(book.getId());
                    assertThat(response.getResponseBody().title()).isEqualTo(book.getTitle());
                    assertThat(response.getResponseBody().author()).isEqualTo(book.getAuthor());
                    assertThat(response.getResponseBody().year()).isEqualTo(book.getYear());
                });
    }

    @Test
    void testCreateBook() {
        Book book = getDefaultBook();
        when(bookService.saveBook(any(Book.class))).thenReturn(Mono.just(book));

        CreateBookRequest createBookRequest = new CreateBookRequest("title", "author", 2023);

        webTestClient.post()
                .uri(API_BOOKS_URL)
                .body(Mono.just(createBookRequest), CreateBookRequest.class)
                .exchange()
                .expectStatus().isCreated()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBody(BookResponse.class)
                .consumeWith(response -> {
                    assertThat(response.getResponseBody()).isNotNull();
                    assertThat(response.getResponseBody().id()).isEqualTo(book.getId());
                    assertThat(response.getResponseBody().title()).isEqualTo(book.getTitle());
                    assertThat(response.getResponseBody().author()).isEqualTo(book.getAuthor());
                    assertThat(response.getResponseBody().year()).isEqualTo(book.getYear());
                });
    }

    @Test
    void testUpdateBook() {
        Book book = getDefaultBook();
        UpdateBookRequest updateBookRequest = new UpdateBookRequest("newTitle", "newActors", 2024);

        when(bookService.validateAndGetBookById(anyString())).thenReturn(Mono.just(book));
        when(bookService.saveBook(any(Book.class))).thenReturn(Mono.just(book));

        webTestClient.patch()
                .uri(API_BOOKS_ID_URL.formatted("123"))
                .body(Mono.just(updateBookRequest), UpdateBookRequest.class)
                .exchange()
                .expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBody(BookResponse.class)
                .consumeWith(response -> {
                    assertThat(response.getResponseBody()).isNotNull();
                    assertThat(response.getResponseBody().id()).isEqualTo(book.getId());
                    assertThat(response.getResponseBody().title()).isEqualTo(updateBookRequest.title());
                    assertThat(response.getResponseBody().author()).isEqualTo(updateBookRequest.author());
                    assertThat(response.getResponseBody().year()).isEqualTo(updateBookRequest.year());
                });
    }

    @Test
    void testDeleteBookWhenExistent() {
        Book book = getDefaultBook();

        when(bookService.validateAndGetBookById(anyString())).thenReturn(Mono.just(book));
        when(bookService.deleteBook(any(Book.class))).thenReturn(Mono.empty());

        webTestClient.delete()
                .uri(API_BOOKS_ID_URL.formatted("123"))
                .exchange()
                .expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBody(BookResponse.class);
    }

    @Test
    void testDeleteBookWhenNonExistent() {
        when(bookService.validateAndGetBookById(anyString())).thenReturn(Mono.error(new BookNotFoundException("123")));

        webTestClient.delete()
                .uri(API_BOOKS_ID_URL.formatted("123"))
                .exchange()
                .expectStatus().isNotFound()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBody(BookResponse.class);
    }

    private Book getDefaultBook() {
        return new Book("123", "title", "author", 2023);
    }

    private static final String API_BOOKS_URL = "/api/books";
    private static final String API_BOOKS_ID_URL = "/api/books/%s";
}

This BookControllerTest is designed to test the functionality of the BookController class, which handles HTTP requests related to books in a reactive Spring WebFlux application. Let's break down the key components of this test class:

Annotations

  • @WebFluxTest(BookController.class): This annotation configures the test to focus only on the components relevant to the BookController. It sets up a minimal Spring application context for testing the web layer;
  • @Import(BookMapperImpl.class): Imports the BookMapperImpl class, indicating that the test will focus on the functionality provided by this specific mapper implementation.

Fields

  • @Autowired WebTestClient webTestClient: Injects a WebTestClient instance, which is a tool for testing reactive web applications. It allows making HTTP requests and asserting the responses;
  • @MockBean BookService bookService: Injects a mock bean for the BookService. The actual implementation is replaced with a mock, allowing control over the service's behavior during testing.

Test Methods

  • testGetBooksWhenThereIsNone(): Tests the GET endpoint for retrieving books when there are none. It expects an empty response and verifies the HTTP status, content type, and body size;
  • testGetBooksWhenThereIsOne(): Tests the GET endpoint for retrieving books when there is one. It expects a response containing the book's details and verifies the HTTP status, content type, and body content;
  • testGetBookByImdbIdWhenNonExistent(): Tests the GET endpoint for retrieving a book by its ID when the book does not exist. It expects a 404 Not Found response;
  • testGetBookByImdbIdWhenExistent(): Tests the GET endpoint for retrieving a book by its ID when the book exists. It expects a response containing the book's details;
  • testCreateBook(): Tests the POST endpoint for creating a new book. It expects a 201 Created response and verifies the created book's details in the response body;
  • testUpdateBook(): Tests the PATCH endpoint for updating an existing book. It expects an 200 OK response and verifies the updated book's details in the response body;
  • testDeleteBookWhenExistent(): Tests the DELETE endpoint for deleting an existing book. It expects a 200 OK response and verifies the deleted book's details in the response body;
  • testDeleteBookWhenNonExistent(): Tests the DELETE endpoint for deleting a non-existing book. It expects a 404 Not Found response.

Running Unit Tests

In a terminal and inside the book-api root folder, run the following command to start the tests:

./mvnw clean test

At the end, all the tests should pass.

Conclusion

In this article, we have implemented unit tests for a Spring Boot Reactive application called Book API. We have added tests for the mapper, service, controller and DTOs. At the end, we ran the test cases to certify that they are all green and passing.

Support and Engagement

If you enjoyed this article and would like to show your support, please consider taking the following actions:

  • 👏 Engage by clapping, highlighting, and replying to my story. I’ll be happy to answer any of your questions;
  • 🌐 Share my story on Social Media;
  • 🔔 Follow me on: Medium | LinkedIn | Twitter;
  • ✉️ Subscribe to my newsletter, so you don’t miss out on my latest posts.
Testing
Unit Testing
Reactive
Spring Boot
Technology
Recommended from ReadMedium