Unit Testing Spring WebFlux CRUD REST API using JUnit and Mockito

In this tutorial, we will learn how to unit test Spring WebFlux controller (Reactive CRUD REST APIs) using JUnit and Mockito frameworks.

We are going to use @WebFluxTest annotation to unit test the Spring WebFlux controller.

@WebFluxTest annotation

@WebFluxTest annotation used to test Spring WebFlux controllers. 

This annotation creates an application context that contains all the beans necessary for testing a Spring WebFlux controller. 

@WebFluxTest is used in combination with @MockBean to provide mock implementations for required collaborators. 

@WebFluxTest also auto-configures WebTestClient, which offers a powerful way to quickly test WebFlux controllers without needing to start a full HTTP server.

WebTestClient 

It is a non-blocking, reactive client for testing web servers that uses the reactive WebClient internally to perform requests and provides a fluent API to verify responses. It can connect to any server over an HTTP, or bind directly to WebFlux applications using mock request and response objects, without needing an HTTP server. 

We are using the below WebTestClient class method to prepare and call CRUD REST API requests:
post() - Prepare an HTTP POST request.
delete() - Prepare an HTTP DELETE request. 
get() - Prepare an HTTP GET request. 
put() - Prepare an HTTP PUT request.

JUnit 5 Framework

It's the de facto standard testing framework for Java.

The current version of JUnit is 5+. The main goal of JUnit 5 is to support Java 8 and above, as well as enable many different styles of testing.

Mockito 4 (Latest) 

Mockito is a mocking framework. It is a Java-based library used to create simple and basic test APIs for performing unit testing of Java applications.

The main purpose of using the Mockito framework is to simplify the development of a test by mocking external dependencies and using them in the test code.

Learn about the Mockito framework at https://site.mockito.org/

Tools and technologies used

  • Java 17+
  • Spring Boot 3
  • Lombok
  • JUnit 5 Framework
  • JUnit
  • JsonPath
  • Mockito
  • IntelliJ IDEA
  • Maven

Prerequisites - Build Reactive CRUD REST APIs

Refer to this tutorial to build Reactive CRUD REST APIs: Spring WebFlux Reactive CRUD REST API Example

Once you build Reactive CRUD REST APIs using WebFlux. Next, continue this tutorial to unit test Spring WebFlux Controller - CRUD REST APIs.

Unit Testing Spring WebFlux Controller (Reactive CRUD REST APIs) using JUnit and Mockito

Set up the Integration Tests Class

Let's create a base to write Unit test cases for Reactive CRUD REST APIs using WebClientTest:
package net.javaguides.springboot;

import net.javaguides.springboot.controller.EmployeeController;
import net.javaguides.springboot.service.EmployeeService;
import org.junit.jupiter.api.extension.ExtendWith;
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.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@WebFluxTest(controllers = EmployeeController.class)
public class EmployeeControllerTests {

    @Autowired
    private WebTestClient webTestClient;

    @MockBean
    private EmployeeService employeeService;

}

@ExtendWith annotation is used to register the SpringExtesion extension.

SpringExtension integrates the Spring TestContext Framework into JUnit 5's Jupiter programming model.

@WebFluxTest annotation used to test Spring WebFlux EmployeeController.

The @MockBean annotation tells Spring to create a mock instance of EmployeeService and add it to the application context so that it's injected into EmployeeController. We have a handle on it in the test so that we can define its behavior before running each test.

WebTestClient class to call the reactive CRUD REST APIs.

Write Unit test Save Employee REST API

Here is the Unit test case to test reactive Save Employee REST API:
@Test
    public void givenEmployeeObject_whenCreateEmployee_thenReturnSavedEmployee() throws Exception {

        // given - precondition or setup
        EmployeeDto employee = EmployeeDto.builder()
                .firstName("Ramesh")
                .lastName("Fadatare")
                .email("[email protected]")
                .build();

        given(employeeService.saveEmployee(any(EmployeeDto.class)))
                .willReturn(Mono.just(employee));

        // when - action or behaviour that we are going test
        WebTestClient.ResponseSpec response = webTestClient.post().uri("/api/employees")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .body(Mono.just(employee), EmployeeDto.class)
                .exchange();

        // then - verify the result or output using assert statements
        response.expectStatus().isCreated()
                .expectBody()
                .consumeWith(System.out::println)
                .jsonPath("$.firstName").isEqualTo(employee.getFirstName())
                .jsonPath("$.lastName").isEqualTo(employee.getLastName())
                .jsonPath("$.email").isEqualTo(employee.getEmail());
    }

Write Unit test Get Employee REST API

Here is the Unit test case to test reactive Get Employee REST API:
@Test
    public void givenEmployeeId_whenGetEmployee_thenReturnEmployeeObject() {

        // given - precondition or setup
        String employeeId = "123";
        EmployeeDto employee = EmployeeDto.builder()
                .firstName("John")
                .lastName("Cena")
                .email("[email protected]")
                .build();

        given(employeeService.getEmployee(employeeId)).willReturn(Mono.just(employee));

        // when - action or behaviour that we are going test
        WebTestClient.ResponseSpec response = webTestClient.get()
                .uri("/api/employees/{id}", Collections.singletonMap("id", employeeId))
                .exchange();

        // then - verify the result or output using assert statements
        response.expectStatus().isOk()
                .expectBody()
                .consumeWith(System.out::println)
                .jsonPath("$.firstName").isEqualTo(employee.getFirstName())
                .jsonPath("$.lastName").isEqualTo(employee.getLastName())
                .jsonPath("$.email").isEqualTo(employee.getEmail());
    }

Write Unit test Get All Employees REST API

Here is the Unit test case to test reactive Get All Employees REST API:
@Test
    public void givenListOfEmployees_whenGetAllEmployees_thenReturnEmployeesList() {

        // given - precondition or setup
        List<EmployeeDto> listOfEmployees = new ArrayList<>();
        listOfEmployees.add(EmployeeDto.builder().firstName("Ramesh").lastName("Fadatare").email("[email protected]").build());
        listOfEmployees.add(EmployeeDto.builder().firstName("Tony").lastName("Stark").email("[email protected]").build());
        Flux<EmployeeDto> employeeFlux = Flux.fromIterable(listOfEmployees);
        given(employeeService.getAllEmployees()).willReturn(employeeFlux);

        // when - action or behaviour that we are going test
        WebTestClient.ResponseSpec response = webTestClient.get().uri("/api/employees")
                .accept(MediaType.APPLICATION_JSON)
                .exchange();

        // then - verify the result or output using assert statements
        response.expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBodyList(EmployeeDto.class)
                .consumeWith(System.out::println);
    }

Unit Test Update Employee REST API

Here is the Unit test case to test reactive Update Employee REST API:
    @Test
    public void givenUpdatedEmployee_whenUpdateEmployee_thenReturnUpdatedEmployeeObject() throws Exception {

        // given - precondition or setup
        String employeeId = "123";
        EmployeeDto employee = EmployeeDto.builder()
                .firstName("Ramesh")
                .lastName("Fadatare")
                .email("[email protected]")
                .build();

        // when - action or behaviour that we are going test
        given(employeeService.updateEmployee(any(EmployeeDto.class), any(String.class)))
                .willReturn(Mono.just(employee));

        // when - action or behaviour that we are going test
        WebTestClient.ResponseSpec response = webTestClient.put()
                .uri("api/employees/{id}", Collections.singletonMap("id", employeeId))
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .body(Mono.just(employee), EmployeeDto.class)
                .exchange();

        // then - verify the result or output using assert statements
        response.expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBody()
                .consumeWith(System.out::println)
                .jsonPath("$.firstName").isEqualTo(employee.getFirstName())
                .jsonPath("$.lastName").isEqualTo(employee.getLastName());
    }

Unit Test Delete Employee REST API

Here is the Unit test case to test reactive Delete Employee REST API:
    @Test
    public void givenEmployeeId_whenDeleteEmployee_thenReturnNothing() {

        // given - precondition or setup
        String employeeId = "123";
        Mono<Void> voidReturn  = Mono.empty();
        given(employeeService.deleteEmployee(employeeId)).willReturn(voidReturn);

        // when - action or behaviour that we are going test
        WebTestClient.ResponseSpec response = webTestClient.delete()
                .uri("/api/employees/{id}", Collections.singletonMap("id",  employeeId))
                .exchange();

        // then - verify the result or output using assert statements
        response.expectStatus().isNoContent()
                .expectBody()
                .consumeWith(System.out::println);
    }

Complete Code to Unit Test Reactive CRUD REST APIs using @WebFluxTest and WebClientTest

We are using the below WebTestClient class method to prepare CRUD REST API requests:
post() - Prepare an HTTP POST request.
delete() - Prepare an HTTP DELETE request. 
get() - Prepare an HTTP GET request. 
put() - Prepare an HTTP PUT request.

Here is the complete code for unit testing Spring WebFlux Reactive CRUD Rest API:
package net.javaguides.springbootwebfluxdemo;

import net.javaguides.springbootwebfluxdemo.controller.EmployeeController;
import net.javaguides.springbootwebfluxdemo.dto.EmployeeDto;
import net.javaguides.springbootwebfluxdemo.service.EmployeeService;
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.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.BDDMockito.*;

@ExtendWith(SpringExtension.class)
@WebFluxTest(controllers = EmployeeController.class)
public class EmployeeControllerUnitTests {

    @Autowired
    private WebTestClient webTestClient;

    @MockBean
    private EmployeeService employeeService;

    @Test
    public void givenEmployeeObject_whenCreateEmployee_thenReturnSavedEmployee() throws Exception {

        // given - precondition or setup
        EmployeeDto employee = EmployeeDto.builder()
                .firstName("Ramesh")
                .lastName("Fadatare")
                .email("[email protected]")
                .build();

        given(employeeService.saveEmployee(any(EmployeeDto.class)))
                .willReturn(Mono.just(employee));

        // when - action or behaviour that we are going test
        WebTestClient.ResponseSpec response = webTestClient.post().uri("/api/employees")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .body(Mono.just(employee), EmployeeDto.class)
                .exchange();

        // then - verify the result or output using assert statements
        response.expectStatus().isCreated()
                .expectBody()
                .consumeWith(System.out::println)
                .jsonPath("$.firstName").isEqualTo(employee.getFirstName())
                .jsonPath("$.lastName").isEqualTo(employee.getLastName())
                .jsonPath("$.email").isEqualTo(employee.getEmail());
    }

    @Test
    public void givenEmployeeId_whenGetEmployee_thenReturnEmployeeObject() {

        // given - precondition or setup
        String employeeId = "123";
        EmployeeDto employee = EmployeeDto.builder()
                .firstName("John")
                .lastName("Cena")
                .email("[email protected]")
                .build();

        given(employeeService.getEmployee(employeeId)).willReturn(Mono.just(employee));

        // when - action or behaviour that we are going test
        WebTestClient.ResponseSpec response = webTestClient.get()
                .uri("/api/employees/{id}", Collections.singletonMap("id", employeeId))
                .exchange();

        // then - verify the result or output using assert statements
        response.expectStatus().isOk()
                .expectBody()
                .consumeWith(System.out::println)
                .jsonPath("$.firstName").isEqualTo(employee.getFirstName())
                .jsonPath("$.lastName").isEqualTo(employee.getLastName())
                .jsonPath("$.email").isEqualTo(employee.getEmail());
    }

    @Test
    public void givenListOfEmployees_whenGetAllEmployees_thenReturnEmployeesList() {

        // given - precondition or setup
        List<EmployeeDto> listOfEmployees = new ArrayList<>();
        listOfEmployees.add(EmployeeDto.builder().firstName("Ramesh").lastName("Fadatare").email("[email protected]").build());
        listOfEmployees.add(EmployeeDto.builder().firstName("Tony").lastName("Stark").email("[email protected]").build());
        Flux<EmployeeDto> employeeFlux = Flux.fromIterable(listOfEmployees);
        given(employeeService.getAllEmployees()).willReturn(employeeFlux);

        // when - action or behaviour that we are going test
        WebTestClient.ResponseSpec response = webTestClient.get().uri("/api/employees")
                .accept(MediaType.APPLICATION_JSON)
                .exchange();

        // then - verify the result or output using assert statements
        response.expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBodyList(EmployeeDto.class)
                .consumeWith(System.out::println);
    }

    @Test
    public void givenUpdatedEmployee_whenUpdateEmployee_thenReturnUpdatedEmployeeObject() throws Exception {

        // given - precondition or setup
        String employeeId = "123";
        EmployeeDto employee = EmployeeDto.builder()
                .firstName("Ramesh")
                .lastName("Fadatare")
                .email("[email protected]")
                .build();

        // when - action or behaviour that we are going test
        given(employeeService.updateEmployee(any(EmployeeDto.class), any(String.class)))
                .willReturn(Mono.just(employee));

        // when - action or behaviour that we are going test
        WebTestClient.ResponseSpec response = webTestClient.put()
                .uri("api/employees/{id}", Collections.singletonMap("id", employeeId))
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .body(Mono.just(employee), EmployeeDto.class)
                .exchange();

        // then - verify the result or output using assert statements
        response.expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBody()
                .consumeWith(System.out::println)
                .jsonPath("$.firstName").isEqualTo(employee.getFirstName())
                .jsonPath("$.lastName").isEqualTo(employee.getLastName());
    }

    @Test
    public void givenEmployeeId_whenDeleteEmployee_thenReturnNothing() {

        // given - precondition or setup
        String employeeId = "123";
        Mono<Void> voidReturn  = Mono.empty();
        given(employeeService.deleteEmployee(employeeId)).willReturn(voidReturn);

        // when - action or behaviour that we are going test
        WebTestClient.ResponseSpec response = webTestClient.delete()
                .uri("/api/employees/{id}", Collections.singletonMap("id",  employeeId))
                .exchange();

        // then - verify the result or output using assert statements
        response.expectStatus().isNoContent()
                .expectBody()
                .consumeWith(System.out::println);
    }
}

Output:

Here is the output of all the JUnit test cases:

Conclusion

In this tutorial, we learned how to unit test the Spring WebFlux controller (Reactive CRUD REST APIs) using JUnit and Mockito frameworks.

Related Tutorials

Comments