Testing in Spring Boot

Testing is an essential part of software development to ensure that your application works as expected and to catch bugs early in the development process. Spring Boot provides extensive support for testing with various testing frameworks like JUnit 5, Mockito, and Spring's testing support. In this tutorial, we'll cover how to set up and perform different types of tests in a Spring Boot 3.2 application.

Prerequisites

  • JDK 17 or later
  • Maven or Gradle
  • IDE (IntelliJ IDEA, Eclipse, etc.)

Step 1: Set Up a Spring Boot Project

1.1 Create a New Spring Boot Project

Use Spring Initializr to create a new project with the following dependencies:

  • Spring Web
  • Spring Boot Test

Download and unzip the project, then open it in your IDE.

Step 2: Add Testing Dependencies

Ensure you have the necessary test dependencies in your pom.xml (for Maven) or build.gradle (for Gradle).

For Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

For Gradle:

testImplementation 'org.springframework.boot:spring-boot-starter-test'

Step 3: Create a Simple Spring Boot Application

3.1 Create a Model Class

Create a simple model class to represent the data in your application.

package com.example.demo.model;

public class User {

    private Long id;
    private String name;
    private String email;

    // Getters and setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

3.2 Create a Service Class

Create a service class that performs business logic.

package com.example.demo.service;

import com.example.demo.model.User;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
public class UserService {

    private final List<User> users = new ArrayList<>();

    public List<User> getAllUsers() {
        return users;
    }

    public Optional<User> getUserById(Long id) {
        return users.stream().filter(user -> user.getId().equals(id)).findFirst();
    }

    public User addUser(User user) {
        users.add(user);
        return user;
    }

    public void deleteUserById(Long id) {
        users.removeIf(user -> user.getId().equals(id));
    }
}

3.3 Create a REST Controller

Create a REST controller to handle HTTP requests.

package com.example.demo.controller;

import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        Optional<User> user = userService.getUserById(id);
        return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
    }

    @PostMapping
    public ResponseEntity<User> addUser(@RequestBody User user) {
        User createdUser = userService.addUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUserById(@PathVariable Long id) {
        userService.deleteUserById(id);
        return ResponseEntity.noContent().build();
    }
}

Step 4: Write Unit Tests

Unit tests focus on testing individual application components in isolation. We'll use JUnit 5 and Mockito for unit testing.

4.1 Test the Service Layer

Create a test class for the UserService class.

package com.example.demo.service;

import com.example.demo.model.User;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;

class UserServiceTest {

    private UserService userService;

    @BeforeEach
    void setUp() {
        userService = new UserService();
    }

    @Test
    void getAllUsers_shouldReturnEmptyList() {
        List<User> users = userService.getAllUsers();
        assertThat(users).isEmpty();
    }

    @Test
    void addUser_shouldAddUser() {
        User user = new User();
        user.setId(1L);
        user.setName("John Doe");
        user.setEmail("john.doe@example.com");

        userService.addUser(user);

        List<User> users = userService.getAllUsers();
        assertThat(users).hasSize(1);
        assertThat(users.get(0)).isEqualTo(user);
    }

    @Test
    void getUserById_shouldReturnUser() {
        User user = new User();
        user.setId(1L);
        user.setName("John Doe");
        user.setEmail("john.doe@example.com");

        userService.addUser(user);

        Optional<User> retrievedUser = userService.getUserById(1L);
        assertThat(retrievedUser).isPresent();
        assertThat(retrievedUser.get()).isEqualTo(user);
    }

    @Test
    void deleteUserById_shouldRemoveUser() {
        User user = new User();
        user.setId(1L);
        user.setName("John Doe");
        user.setEmail("john.doe@example.com");

        userService.addUser(user);
        userService.deleteUserById(1L);

        List<User> users = userService.getAllUsers();
        assertThat(users).isEmpty();
    }
}

Explanation:

  • @BeforeEach: Initializes the UserService before each test.
  • @Test: Marks a method as a test method.
  • assertThat: Provided by AssertJ, used for making assertions about the test results.

Step 5: Write Integration Tests

Integration tests verify that different parts of the application work together as expected.

5.1 Test the REST Controller

Create a test class for the UserController class.

package com.example.demo.controller;

import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import java.util.Collections;
import java.util.Optional;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    private User user;

    @BeforeEach
    void setUp() {
        user = new User();
        user.setId(1L);
        user.setName("John Doe");
        user.setEmail("john.doe@example.com");
    }

    @Test
    void getAllUsers_shouldReturnEmptyList() throws Exception {
        Mockito.when(userService.getAllUsers()).thenReturn(Collections.emptyList());

        mockMvc.perform(get("/users"))
               .andExpect(MockMvcResultMatchers.status().isOk())
               .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(0));
    }

    @Test
    void addUser_shouldCreateUser() throws Exception {
        Mockito.when(userService.addUser(Mockito.any(User.class))).thenReturn(user);

        mockMvc.perform(post("/users")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"name\": \"John Doe\", \"email\": \"john.doe@example.com\"}"))
               .andExpect(MockMvcResultMatchers.status().isCreated())
               .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1L))
               .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("John Doe"))
               .andExpect(MockMvcResultMatchers.jsonPath("$.email").value("john.doe@example.com"));
    }

    @Test
    void getUserById_shouldReturnUser() throws Exception {
        Mockito.when(userService.getUserById(1L)).thenReturn(Optional.of(user));

        mockMvc.perform(get("/users

/1"))
               .andExpect(MockMvcResultMatchers.status().isOk())
               .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1L))
               .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("John Doe"))
               .andExpect(MockMvcResultMatchers.jsonPath("$.email").value("john.doe@example.com"));
    }

    @Test
    void deleteUserById_shouldReturnNoContent() throws Exception {
        mockMvc.perform(delete("/users/1"))
               .andExpect(MockMvcResultMatchers.status().isNoContent());
    }
}

Explanation:

  • @WebMvcTest(UserController.class): Sets up a test context for the UserController class and configures the necessary beans for testing web MVC controllers.
  • @MockBean: Creates a mock of the UserService bean.
  • MockMvc: Provides a powerful way to perform and verify HTTP requests and responses.
  • MockMvcResultMatchers: Provides matchers for validating responses.

Step 6: Running the Tests

6.1 Run the Tests

You can run the tests using your IDE or from the command line using Maven or Gradle.

For Maven:

./mvnw test

For Gradle:

./gradlew test

6.2 Verify the Test Results

Ensure that all tests pass successfully. The unit tests should verify the correctness of the UserService, and the integration tests should verify the REST API endpoints provided by the UserController.

Conclusion

In this tutorial, you have learned how to set up and perform different types of tests in a Spring Boot 3.2 application. We covered:

  • Setting up a Spring Boot project
  • Creating a simple application with a model, service, and controller
  • Writing unit tests using JUnit 5 and Mockito
  • Writing integration tests for the REST controller using @WebMvcTest and MockMvc

Testing is a crucial part of developing reliable and maintainable software. By leveraging Spring Boot's powerful testing support, you can ensure your application behaves as expected and catch issues early in the development process.


Comments