In this tutorial, we'll explore how to use Spring Boot's WebTestClient for testing CRUD (Create, Read, Update, Delete) operations in a RESTful service. WebTestClient is a non-blocking, reactive web client for testing web components.
WebTestClient is a client-side test tool that is part of the Spring WebFlux module. It's designed to test reactive and non-reactive web applications by performing requests and asserting responses without the need for running a server. WebTestClient is particularly useful for integration testing, where it can mimic the behavior of client requests and validate the responses from your RESTful services.
Non-Blocking Client: Suitable for testing reactive applications with asynchronous and event-driven behavior.
Fluent API: Offers a fluent API for building requests, sending them, and asserting responses.
Support for Both Web MVC and WebFlux: Works with both traditional servlet-based and reactive-based web applications.
In this tutorial, we'll create a Spring Boot application that performs CRUD operations on a User entity, using an H2 in-memory database for persistence. We'll then test these CRUD operations using WebTestClient. The application will be structured into three layers: Repository, Service, and Controller.
Ensure you have the following dependencies in your pom.xml:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
import jakarta.persistence.*; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String firstName; private String lastName; private String email; // Constructors, Getters, Setters }
import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Long> { }
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; @Service public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public User createUser(User user) { return userRepository.save(user); } public Optional<User> getUser(Long id) { return userRepository.findById(id); } public User updateUser(Long id, User userDetails) { User user = userRepository.findById(id).orElseThrow(); user.setFirstName(userDetails.getFirstName()); user.setLastName(userDetails.getLastName()); user.setEmail(userDetails.getEmail()); return userRepository.save(user); } public void deleteUser(Long id) { userRepository.deleteById(id); } public List<User> getAllUsers() { return userRepository.findAll(); } }
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/users") public class UserController { private final UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @PostMapping public ResponseEntity<User> createUser(@RequestBody User user) { return ResponseEntity.ok(userService.createUser(user)); } @GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { return userService.getUser(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @GetMapping public ResponseEntity<List<User>> getAllUsers() { return ResponseEntity.ok(userService.getAllUsers()); } @PutMapping("/{id}") public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) { return ResponseEntity.ok(userService.updateUser(id, user)); } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteUser(@PathVariable Long id) { userService.deleteUser(id); return ResponseEntity.ok().build(); } }
First, configure WebTestClient in your test class:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureWebTestClient public class UserControllerTest { @Autowired private WebTestClient webTestClient; // Test methods go here }
Now, let's write tests for each CRUD operation - create, retrieve, update, and delete User entities, and assert the responses using WebTestClient.
For our test cases, we'll need a sample User object.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureWebTestClient public class UserControllerTest { @Autowired private WebTestClient webTestClient; private User sampleUser; @BeforeEach void setUp() { sampleUser = new User(); sampleUser.setFirstName("John"); sampleUser.setLastName("Doe"); sampleUser.setEmail("john.doe@example.com"); } // Test methods will be added here }
Testing the creation of a new User.
@Test public void createUserTest() { webTestClient.post() .uri("/users") .contentType(MediaType.APPLICATION_JSON) .bodyValue(sampleUser) .exchange() .expectStatus().isOk() .expectBody() .jsonPath("$.firstName").isEqualTo("John") .jsonPath("$.lastName").isEqualTo("Doe") .jsonPath("$.email").isEqualTo("john.doe@example.com"); }
Testing retrieval of a User.
@Test public void getUserTest() { Long userId = 1L; // Assuming this ID exists in the database webTestClient.get() .uri("/users/" + userId) .exchange() .expectStatus().isOk() .expectBody() .jsonPath("$.id").isEqualTo(userId) .jsonPath("$.firstName").isEqualTo("John"); }
Testing the update of a User.
@Test public void updateUserTest() { Long userId = 1L; // Assuming this ID exists User updatedUser = new User(); updatedUser.setFirstName("Jane"); updatedUser.setLastName("Doe"); updatedUser.setEmail("jane.doe@example.com"); webTestClient.put() .uri("/users/" + userId) .contentType(MediaType.APPLICATION_JSON) .bodyValue(updatedUser) .exchange() .expectStatus().isOk() .expectBody() .jsonPath("$.firstName").isEqualTo("Jane") .jsonPath("$.lastName").isEqualTo("Doe"); }
Testing the deletion of a User.
@Test public void deleteUserTest() { Long userId = 1L; // Assuming this ID exists webTestClient.delete() .uri("/users/" + userId) .exchange() .expectStatus().isOk(); }
This tutorial covered creating a simple Spring Boot application with a User entity and performing CRUD operations using an H2 database. The application is structured into repository, service, and controller layers, and we tested these operations using WebTestClient, demonstrating the tool's effectiveness for testing web layers in Spring Boot applications.