Open In App

Testing Spring WebFlux Reactive CRUD Rest APIs Using WebTestClient

Spring Boot is one of the famous frameworks for Back-end development. The Spring Boot Community Developed the Spring Reactive Web Framework. This Reactive Framework is available from Spring Boot 5.0 and Above versions only. The Spring Reactive allows developers to build Asynchronous, Non-Blocking, and Event-Driven Web Applications. When compared with the Spring MVC framework the Spring Reactive Web framework provides more functionality. And we can easily manage the web applications through this Spring Reactive Framework. If we want to use this framework, we need to integrate Spring Web flux in our dependencies File.

For Developing this project, we have used MongoDB as a database, here every CRUD Operation we have created one REST API endpoint, These API endpoints are defined by using RouterFunction and It returns a route function.



Prerequisites:

To understand or create this project you have strong knowledge of Listed Concepts.

Tools and Technologies:

Below are the Tools and Technologies, we have used to create this project.



Project Creation:

Project Folder Structure:

Project Dependencies:

In this project, we have used below Dependencies and the Project category is gradle.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

MongoDB Connection with Spring Reactive

In application.properties file, configure the database connection with required properties.

spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=working

Spring WebFlux Reactive CRUD Rest APIs using WebTestClient

In this project, main package we have created different classes for different purposes. Each java class perform different actions. We can observe in the Project Folder image above. Below we have listed all the classes and explained with their functionality.

Student POJO class:

Here, we have created one POJO class handling Database operations by using lombok dependency. This provides setters and getters with all types of constructors. This class have three attributes namely id, studentName, studentAge. The id is automatically generated by the MongoDB while creating new record in collection.




package com.webflux.app;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
  
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "student")
public class Student {
    @Id
    private String id;
    private String studentName;
    private String studentAge;
}

UserRepo Interface

This UserRepo interface is used for performing database related operation on Student model class. This interface extends to ReactiveMongoRepository. This repository is created by using @Repository, @EnableReactiveMongoRepositories. And ReactiveMongoRepository take two arguments as input those are targeted POJO class and It’s Id data type.




package com.webflux.app;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
import org.springframework.stereotype.Repository;
  
@Repository
@EnableReactiveMongoRepositories
public interface UserRepo extends ReactiveMongoRepository<Student, String> {
  
}

ServiceHandler class

This is a service layer for creating API logic here. In this class, we have created four APIs for performing CRUD operation by using REST API. Here The service class is created by using @Service annotation, then by using UserRepo we perform the CRUD operation.




package com.webflux.app;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
  
@Service
public class ServiceHandler {
  
    @Autowired
    private UserRepo userRepo;
  
    public Mono<ServerResponse> addStudent(ServerRequest request) {
        return request.bodyToMono(Student.class).flatMap(data -> {
            return ServerResponse.ok().body(userRepo.save(data), Student.class);
        });
    }
  
    public Mono<ServerResponse> deleteStudentById(ServerRequest request) {
        return request.bodyToMono(Student.class).flatMap(data -> {
            return ServerResponse.ok().body(userRepo.deleteById(data.getId()), Student.class);
        }).switchIfEmpty(ServerResponse.ok().bodyValue("No Student Data Found"));
    }
  
    public Mono<ServerResponse> updateStudentById(ServerRequest request) {
        return request.bodyToMono(Student.class).flatMap(data -> {
            return userRepo.findById(data.getId()).flatMap(change -> {
                change.setId(data.getId());
                change.setStudentName(data.getStudentName());
                change.setStudentAge(data.getStudentAge());
                return ServerResponse.ok().body(userRepo.save(change), Student.class);
            }).switchIfEmpty(ServerResponse.ok().bodyValue("No Student Data Found"));
        });
    }
  
    public Mono<ServerResponse> getAllStudents(ServerRequest request) {
        return request.bodyToMono(Student.class).flatMap(data -> {
            return ServerResponse.ok().body(userRepo.findAll(), Student.class);
        }).switchIfEmpty(ServerResponse.ok().bodyValue("No Student Data Found"));
    }
}

ServiceRouter class

This another Java class for handling REST API end point. This class is created by using @Configuration annotation and this class have one bean that is RouterFunction. This bean creates routing end points for ServiceHandler API logic’s. This router class have four REST API end points to handle CRUD operations.




package com.webflux.app;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
  
@Configuration
public class ServiceRouter {
      
    @Autowired
    private ServiceHandler serviceHandler;
      
    @Bean
    RouterFunction<ServerResponse> routerFunction(){
        return RouterFunctions.route(RequestPredicates.POST("api/student/add"),serviceHandler::addStudent)
                .andRoute(RequestPredicates.POST("api/student/delete"), serviceHandler::deleteStudentById)
                .andRoute(RequestPredicates.POST("api/student/update"), serviceHandler::updateStudentById)
                .andRoute(RequestPredicates.POST("api/student/getall"), serviceHandler::getAllStudents)
                ;
                  
    }
      
}

addStudent REST API

This API logic is used for creating a new Student row in the Collection. Here, we have used Mono Publisher and It return ServerResponse as a output. In this, first we convert Student POJO class body to Mono publisher by using bodyToMono(). Then we have created one flatmap , in this, we have created one lambda expression for save the data. Here, we save the Student data by using object of UserRepo repository. And It’s REST API URL is defined in ServiceRouter.

REST API URL : api/student/add




public Mono<ServerResponse>
addStudent(ServerRequest request)
{
    return request.bodyToMono(Student.class)
        .flatMap(data -> {
            return ServerResponse.ok().body(
                userRepo.save(data), Student.class);
        });
}

When we test this API URL by using POSTMAN tool, successfully data is inserted into a Collection. Below is the output image for your reference. When we hit this API URL the data is saved into collection and return that saved record as a output.

Output:

deleteStudentById REST API

This API logic is used for delete a existing student data by using Student id, If id is available student data will be deleted otherwise It gives some error message as output. In this API logic we have created one flatmap for finding id, If id exist then deleted otherwise switchIfEmpty() method is executed.

REST API URL : api/student/delete




public Mono<ServerResponse> deleteStudentById(ServerRequest request) {
    return request.bodyToMono(Student.class).flatMap(data -> {
        return ServerResponse.ok().body(userRepo.deleteById(data.getId()), Student.class);
    }).switchIfEmpty(ServerResponse.ok().bodyValue("No Student Data Found"));
}

Output:

updateStudentById REST API

This API is used for update the existing Student data by using the Student ID. If Student Id not found it gives an error message other wise successfully update new data for existing id then return the new data as server response. We can observe this in the output image below.

REST API URL : api/student/update




public Mono<ServerResponse> updateStudentById(ServerRequest request) {
        return request.bodyToMono(Student.class).flatMap(data -> {
            return userRepo.findById(data.getId()).flatMap(change -> {
                change.setId(data.getId());
                change.setStudentName(data.getStudentName());
                change.setStudentAge(data.getStudentAge());
                return ServerResponse.ok().body(userRepo.save(change), Student.class);
            }).switchIfEmpty(ServerResponse.ok().bodyValue("No Student Data Found"));
        });
    }

Output:

getAllStudents REST API

This REST API return all existing student data from collection, below is the Java code and Its output image for understanding in better way.

REST API URL : api/student/update




public Mono<ServerResponse> getAllStudents(ServerRequest request) {
        return request.bodyToMono(Student.class).flatMap(data -> {
            return ServerResponse.ok().body(userRepo.findAll(), Student.class);
        }).switchIfEmpty(ServerResponse.ok().bodyValue("No Student Data Found"));
    }

We have two documents only, when we hit this API return that two Records data from Database. Below is the output image.

Output:

WebTestClient

The WebTestClient is an HTTP client used for testing Server related Applications. In our case in the above we developed Reactive REST APIs. Now we need Test those APIs. Then we get a conclusion means the APIs working properly or not. For understanding his Testing code, you should have knowledge in testing. Below we have provided the Test code. Write this testing code in Test class. This class available in Project Test package. Below we have provided the Testing code for four APIs with output image for your reference.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient

With out these Spring Boot Annotations with given properties, We have a chance to get errors.




package com.webflux.app;
  
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
  
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.server.ServerResponse;
  
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class WebFluxApplicationTests {
    @Autowired
    private WebTestClient webTestClient;
      
    @MockBean
    private ServiceHandler serviceHandler;
      
      
    @Test
    public void testaddStudent() {
        Student student = new Student();
        student.setStudentName("John");
        student.setStudentAge("21");
          
        when(serviceHandler.addStudent(any())).thenReturn(ServerResponse.ok().bodyValue(student));
        webTestClient.post().uri("/api/student/add").contentType(MediaType.APPLICATION_JSON)
        .bodyValue(student).exchange().expectStatus().isOk().expectBody(Student.class)
        .isEqualTo(student);
    }
      
    @Test
    public void testdeleteStudentById() {
        Student student = new Student();
        student.setId("1234");
  
        when(serviceHandler.deleteStudentById(any())).thenReturn(ServerResponse.ok().bodyValue(student));
        webTestClient.post().uri("/api/student/delete").contentType(MediaType.APPLICATION_JSON)
        .bodyValue(student).exchange().expectStatus().isOk().expectBody(Student.class)
        .isEqualTo(student);
    }
  
    @Test
    public void testupdateStudentById() {
        Student student = new Student();
        student.setId("1234");
        student.setStudentName("John");
        student.setStudentAge("21");
  
        when(serviceHandler.updateStudentById(any())).thenReturn(ServerResponse.ok().bodyValue(student));
        webTestClient.post().uri("/api/student/update").contentType(MediaType.APPLICATION_JSON)
        .bodyValue(student).exchange().expectStatus().isOk().expectBody(Student.class)
        .isEqualTo(student);
    }
      
    @Test
    public void testgetAllStudents() {
        Student student = new Student();
  
        when(serviceHandler.getAllStudents(any())).thenReturn(ServerResponse.ok().bodyValue(student));
        webTestClient.post().uri("/api/student/getall").contentType(MediaType.APPLICATION_JSON)
        .bodyValue(student).exchange().expectStatus().isOk().expectBody(Student.class)
        .isEqualTo(student);
    }
}

Output:

Explaination of the above Program:


Article Tags :