Open In App

Testing Spring WebFlux Reactive CRUD Rest APIs Using WebTestClient

Last Updated : 26 Feb, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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.

  • In CRUD operation C indicates creating data in MongoDB for this we have created a REST API endpoint, This API takes input in the form of JSON format.
  • The R indicates Retrieve operation, which means we can Retrieve all existing data, or by using ID also we can Retrieve data from the Database.
  • Next U indicates Update operation in CRUD, we can be able to update existing data by using ID.
  • The Final one is D indicates the delete operation. We can delete data from MongoDB by using the existing ID or Delete all Data.

Prerequisites:

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

  • Creation of Spring Boot Starter Project
  • Knowledge in Java Programming
  • Strong Knowledge in Spring WebFlux Reactive framework
  • Basics of MongoDB
  • REST API functionality

Tools and Technologies:

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

  • Spring Tools Suite
  • MongoDB
  • Spring WebFlux Reactive Framework
  • REST APIs

Project Creation:

  • Open Spring Tool Suite Select a New project which Spring Stater type.
  • After that select Project Category like maven or gradle, here we have used Gradle.
  • Then provide the project name, package name, and other things.
  • Click on next, then will get another screen, in this screen we need select Dependencies for our project.
  • Now click on finish.

Project Folder Structure:

Project 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.

Java




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.

Java




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.

Java




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.

Java




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

Java




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:

Create Student Data

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

Java




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:

After Delete

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

Java




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:

After Update

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

Java




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:

Get all Students

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.

Java




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:

Test report

Explaination of the above Program:

  • First, we Autowired the ServerHandler class for accessing the APIs methods from Server layer.
  • After that, we Autowired the WebTestClient to access the Service of the WebTestClient to test the applications means APIs.
  • Then, we have created four test methods by using @Test, in each method, we have created Model class object to set and get the data while testing APIs.
  • After this in each class we call the APIs methods by using ServiceHandler object with API URLs using when in Testing.
  • Then we call the webTestClient object to testing the APIs URLs by using post method.
  • After this Test class as Junit, Four APIs are successfully passed the Test cases. Below we have provided that output image.


Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads