Spring-Webflux: How to test your controllers using WebTestClient?

Table of contents
Reading Time: 3 minutes

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 which 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 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 the 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:

  1. In the starting of the test class, we have added the @RunWith(SpringRunner.class). This tells JUnit to run using Spring’s testing support.
  2. 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 used in combination with @MockBean or @Import to create any collaborators required by your @Controller beans.
  3. 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.
  4. 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.
  5. 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 given in the code above.

It’s pretty straight forward. once, the WebTestClinet is declared, 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 Doc

Knoldus-Scala-spark-services-company

Written by 

Vinisha Sharma is a software consultant having more than 6 months of experience. She thrives in a fast pace environment and loves exploring new technologies. She has experience with the languages such as C, C++, Java, Scala and is currently working on Java 8. Her hobbies include sketching and dancing. She believes Optimism and a learning attitude is the key to achieve success in your life