
Introduction :
While working with Spring MVC, you might have used Spring MockMVC to perform testing of Spring web MVC controllers. MockMVC class is part of the Spring MVC test framework which helps in testing the controllers explicitly starting a Servlet container. But, this is not something that will work if you are using SpringBoot Webflux. If you have a Spring application built with Webflux, the MVC controllers can be tested using WebTestClient.
To start with, first, we need to have the following dependency in pom.xml.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
To understand now, how things will work let us try to consider an example of an Employee CRUD application. I have my EmployeeController like this:
@RestController @AllArgsConstructor public class EmployeeController { private EmployeeService employeeService; @GetMapping(value = "/employees/{id}") public Mono<Employee> getEmployeeById(@PathVariable("id") int id) { return employeeService.getEmployeeById(id); } @PostMapping(value = "/employees", consumes = "application/json") @ResponseStatus(HttpStatus.CREATED) public Mono<Employee> createEmployee(@RequestBody Employee employee) { return employeeService.createEmployee(employee); } @DeleteMapping(value = "/employees/{id}") public Mono<String> deleteEmployeeById(@PathVariable("id") int id) { return employeeService.deleteEmployeeById(id); } }
My controller is dependent on EmployeeService for all of its business layer logic.
We need not worry about how things are being done inside EmployeeService because for now, we are writing unit test cases for our EmployeeController.
It’s time to start with EmployeeControllerTest. Let’s write our test cases and then understand, what’s actually happening?
@RunWith(SpringRunner.class) @WebFluxTest(EmployeeController.class) public class EmployeeControllerTest { @Autowired WebTestClient webTestClient; @MockBean private EmployeeService employeeService; @Test public void testGetEmployeeById() { Employee employee = Employee.builder().id(1).city("delhi").age(23).name("ABC").build(); Mono<Employee> employeeMono = Mono.just(employee); when(employeeService.getEmployeeById(1)).thenReturn(employeeMono); webTestClient.get() .uri("/employees/1") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk() .expectBody(Employee.class) .value(employee1 -> employee.getAge(), equalTo(23)); } @Test public void testDeleteEmployeeById() { when(employeeService.deleteEmployeeById(1)).thenReturn(Mono.just("Employee with id 1 is deleted.")); webTestClient.delete() .uri("/employees/1") .exchange() .expectStatus().isOk() .expectBody(String.class) .isEqualTo("Employee with id 1 is deleted."); } @Test public void testCreateEmployee() { Employee employee = Employee.builder().id(1).city("delhi").age(23).name("ABC").build(); Mono<Employee> employeeMono = Mono.just(employee); when(employeeService.createEmployee(employee)).thenReturn(employeeMono); webTestClient.post() .uri("/employees") .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .body(Mono.just(employee), Employee.class) .exchange() .expectStatus().isCreated(); } }
Let’s discuss the concepts used above one by one:
- At the start of the test class, we added the @RunWith(SpringRunner.class). This tells JUnit to run using Spring’s testing support.
- we have also used @WebFluxTest(EmployeeController.class). Using this annotation will disable full auto-configuration and instead apply only configuration relevant to WebFlux tests (i.e. @Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, and WebFluxConfigurer beans but not @Component, @Service, or @Repository beans). Typically @WebFluxTest is utilize in combination with @MockBean or @Import to create any collaborators that need your @Controller beans.
- Inside our test class, we have our webTestClient Object. It is a non-blocking, reactive client for testing web servers. It uses reactive WebClient internally to perform requests and provides a fluent API to verify responses. WebTestClient can connect to any server over an HTTP connection. It can also bind directly to WebFlux applications using mock request and response objects, without the need for an HTTP server.
- We have used @MockBean annotation with EmployeeService. When @MockBean is used on a field, as well as being registered in the application context, the mock will also be injected into the field.
- With WebTestClient we can define the route and its expected response in the test case. The assertions can be done using expectStatus(), expectHeader() or even expectBody(). How to use them is already in the code above.
It’s pretty straightforward. Once, the WebTestClinet is enunciating, just use it to define your routes and their responses. This is how we can leverage WebTestClient to test our controllers.
I hope, you have liked my blog. If you have any doubt or any suggestions to make please drop a comment. Thanks!
References:
spring-5-reactive-webclient-webtestclient-examples
WebTestClient official Do
Conclusion :
In this article, we learned more about Spring Async and Spring WebFlux, then we had a comparison of them theoretically and practically with a basic load test.