Spring Boot TestRestTemplate - Testing CRUD REST APIs

This tutorial will guide you through using TestRestTemplate in Spring Boot for testing CRUD operations on a User entity. We will use an H2 in-memory database for data persistence and create the necessary service, repository, and controller layers.

What is TestRestTemplate?

TestRestTemplate is a template class provided by Spring Boot for integration testing that involves a running server. It is used to make RESTful calls to an actual server and is ideal for full-stack integration testing. 

Advantages of TestRestTemplate

Full-Stack Testing: TestRestTemplate is used for end-to-end testing of the application, including the web layer, server, and often the database.

Realistic Scenarios: It is closer to real-world scenarios where the application is running on an actual server, making it ideal for testing the complete stack.

Ease of Use: It offers a straightforward approach for making REST calls, simplifying the testing of RESTful APIs.

Ideal Use Cases for TestRestTemplate

  • Use TestRestTemplate, where you need to test the application as a whole using Integration testing.
  • Testing RESTful APIs in scenarios that closely mimic the production environment.
  • Situations where you want to test the application's interaction with external services or databases.

Testing Spring Boot CRUD REST APIs using TestRestTemplate 

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 TestRestTemplate. The application will be structured into three layers: Repository, Service, and Controller.

Project Setup 

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-webf</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 TestRestTemplate

First, configure TestRestTemplate in your test class:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerTest {

    @Autowired
    private TestRestTemplate testRestTemplate;

    // Test methods go here
}

Now, write tests for each CRUD operation. Create, retrieve, update, and delete User entities, and assert the responses using TestRestTemplate.

Create (POST)

Testing the creation of a new User.

@Test
public void createUserTest() {
    User newUser = new User(null, "Alice", "Smith", "[email protected]");
    ResponseEntity<User> response = testRestTemplate.postForEntity("/users", newUser, User.class);

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertNotNull(response.getBody().getId());
    assertEquals("Alice", response.getBody().getFirstName());
}

Read (GET)

Testing retrieval of a User.

@Test
public void getUserTest() {
    // Create a user to retrieve
    User newUser = new User(null, "Bob", "Jones", "[email protected]");
    User createdUser = testRestTemplate.postForObject("/users", newUser, User.class);

    ResponseEntity<User> response = testRestTemplate.getForEntity("/users/" + createdUser.getId(), User.class);

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertEquals("Bob", response.getBody().getFirstName());
}

Update (PUT)

Testing the update of a User.

@Test
public void updateUserTest() {
    // Create a user to update
    User newUser = new User(null, "Charlie", "Brown", "[email protected]");
    User createdUser = testRestTemplate.postForObject("/users", newUser, User.class);

    User updatedUser = new User(null, "Charles", "Brown", "[email protected]");
    testRestTemplate.put("/users/" + createdUser.getId(), updatedUser);

    ResponseEntity<User> response = testRestTemplate.getForEntity("/users/" + createdUser.getId(), User.class);

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertEquals("Charles", response.getBody().getFirstName());
}

Delete (DELETE)

Testing the deletion of a User.

@Test
public void deleteUserTest() {
    // Create a user to delete
    User newUser = new User(null, "Dave", "Wilson", "[email protected]");
    User createdUser = testRestTemplate.postForObject("/users", newUser, User.class);

    testRestTemplate.delete("/users/" + createdUser.getId());

    ResponseEntity<User> response = testRestTemplate.getForEntity("/users/" + createdUser.getId(), User.class);

    assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
}

Conclusion

In this tutorial, we've demonstrated how to use TestRestTemplate for testing CRUD operations on a User entity in a Spring Boot application, backed by an H2 in-memory database. This approach is ideal for full-stack integration testing, providing a realistic testing environment while ensuring the correct behavior of the RESTful service.

Comments