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.
What is WebTestClient?
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.
Key Features:
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.
Testing Spring Boot CRUD REST APIs using WebTestClient
Project Setup
<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>
The User Entity
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
}
The UserRepository
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
The UserService
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();
}
}
The UserController
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();
}
}
Writing Tests with WebTestClient
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.
Preparing Test Data
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
}
Create (POST)
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");
}
Read (GET)
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");
}
Update (PUT)
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");
}
Delete (DELETE)
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();
}
Conclusion
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.
Comments
Post a Comment
Leave Comment