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.
JUnit 5 Framework
Mockito 4 (Latest)
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
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
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
@Test
public void givenEmployeeObject_whenCreateEmployee_thenReturnSavedEmployee() throws Exception {
// given - precondition or setup
EmployeeDto employee = EmployeeDto.builder()
.firstName("Ramesh")
.lastName("Fadatare")
.email("ramesh@gmail.com")
.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
@Test
public void givenEmployeeId_whenGetEmployee_thenReturnEmployeeObject() {
// given - precondition or setup
String employeeId = "123";
EmployeeDto employee = EmployeeDto.builder()
.firstName("John")
.lastName("Cena")
.email("john@gmail.com")
.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
@Test
public void givenListOfEmployees_whenGetAllEmployees_thenReturnEmployeesList() {
// given - precondition or setup
List<EmployeeDto> listOfEmployees = new ArrayList<>();
listOfEmployees.add(EmployeeDto.builder().firstName("Ramesh").lastName("Fadatare").email("ramesh@gmail.com").build());
listOfEmployees.add(EmployeeDto.builder().firstName("Tony").lastName("Stark").email("tony@gmail.com").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("ramesh@gmail.com")
.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
@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
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("ramesh@gmail.com")
.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("john@gmail.com")
.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("ramesh@gmail.com").build());
listOfEmployees.add(EmployeeDto.builder().firstName("Tony").lastName("Stark").email("tony@gmail.com").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("ramesh@gmail.com")
.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);
}
}
Comments
Post a Comment
Leave Comment