Testing in Spring Boot: Unit Testing and Integration Testing

Testing is a crucial part of developing robust and reliable applications. Spring Boot provides extensive support for testing applications, including unit tests, integration tests, and end-to-end tests. This guide will walk you through setting up and running tests in a Spring Boot 3 application.

Prerequisites

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

Setting Up the Project

Step 1: Create a New Spring Boot Project

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

  • Spring Web
  • Spring Data JPA
  • H2 Database
  • Spring Boot Starter Test

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

Step 2: Define the Entity, Repository, Service, and Controller

2.1 Create the Student Entity

Create a class named Student in the com.example.demo.entity package.

package com.example.demo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    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;
    }
}

Explanation:

  • @Entity: Marks the class as a JPA entity.
  • @Id and @GeneratedValue: Define the primary key and its generation strategy.
  • name and email: Simple fields representing the student's name and email.

2.2 Create the StudentRepository

Create an interface named StudentRepository in the com.example.demo.repository package.

package com.example.demo.repository;

import com.example.demo.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
}

Explanation:

  • JpaRepository<Student, Long>: Provides CRUD operations for the Student entity.
  • @Repository: Marks the interface as a Spring Data repository.

2.3 Create the StudentService

Create a service class named StudentService in the com.example.demo.service package.

package com.example.demo.service;

import com.example.demo.entity.Student;
import com.example.demo.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

@Service
public class StudentService {

    @Autowired
    private StudentRepository studentRepository;

    public List<Student> getAllStudents() {
        return studentRepository.findAll();
    }

    public Optional<Student> getStudentById(Long id) {
        return studentRepository.findById(id);
    }

    public Student createStudent(Student student) {
        return studentRepository.save(student);
    }

    public void deleteStudent(Long id) {
        studentRepository.deleteById(id);
    }
}

Explanation:

  • @Service: Marks the class as a service component in Spring.
  • @Autowired: Injects the StudentRepository dependency.
  • getAllStudents(), getStudentById(Long id), createStudent(Student student), deleteStudent(Long id): Methods to perform CRUD operations on Student entities.

2.4 Create the StudentController

Create a controller class named StudentController in the com.example.demo.controller package.

package com.example.demo.controller;

import com.example.demo.entity.Student;
import com.example.demo.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

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

@RestController
@RequestMapping("/students")
public class StudentController {

    @Autowired
    private StudentService studentService;

    @GetMapping
    public List<Student> getAllStudents() {
        return studentService.getAllStudents();
    }

    @GetMapping("/{id}")
    public Optional<Student> getStudentById(@PathVariable Long id) {
        return studentService.getStudentById(id);
    }

    @PostMapping
    public Student createStudent(@RequestBody Student student) {
        return studentService.createStudent(student);
    }

    @DeleteMapping("/{id}")
    public void deleteStudent(@PathVariable Long id) {
        studentService.deleteStudent(id);
    }
}

Explanation:

  • @RestController: Marks the class as a REST controller.
  • @RequestMapping("/students"): Maps the controller to /students endpoint.
  • @Autowired: Injects the StudentService dependency.
  • @GetMapping, @PostMapping, @DeleteMapping: Maps HTTP GET, POST, and DELETE requests respectively.

Writing Unit Tests

Unit tests focus on testing individual components in isolation. Spring Boot provides support for writing unit tests with the help of JUnit and Mockito.

Step 3: Write Unit Tests for the Service Layer

3.1 Write Unit Tests for StudentService

Create a test class named StudentServiceTest in the src/test/java/com/example/demo/service package.

package com.example.demo.service;

import com.example.demo.entity.Student;
import com.example.demo.repository.StudentRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class StudentServiceTest {

    @Mock
    private StudentRepository studentRepository;

    @InjectMocks
    private StudentService studentService;

    @Test
    void testGetAllStudents() {
        Student student1 = new Student();
        student1.setName("John Doe");
        student1.setEmail("[email protected]");

        Student student2 = new Student();
        student2.setName("Jane Doe");
        student2.setEmail("[email protected]");

        List<Student> students = Arrays.asList(student1, student2);

        when(studentRepository.findAll()).thenReturn(students);

        List<Student> result = studentService.getAllStudents();

        assertEquals(2, result.size());
        verify(studentRepository, times(1)).findAll();
    }

    @Test
    void testGetStudentById() {
        Student student = new Student();
        student.setId(1L);
        student.setName("John Doe");
        student.setEmail("[email protected]");

        when(studentRepository.findById(1L)).thenReturn(Optional.of(student));

        Optional<Student> result = studentService.getStudentById(1L);

        assertEquals("John Doe", result.get().getName());
        assertEquals("[email protected]", result.get().getEmail());
        verify(studentRepository, times(1)).findById(1L);
    }

    @Test
    void testCreateStudent() {
        Student student = new Student();
        student.setName("John Doe");
        student.setEmail("[email protected]");

        when(studentRepository.save(student)).thenReturn(student);

        Student result = studentService.createStudent(student);

        assertEquals("John Doe", result.getName());
        assertEquals("[email protected]", result.getEmail());
        verify(studentRepository, times(1)).save(student);
    }

    @Test
    void testDeleteStudent() {
        Long studentId = 1L;

        doNothing().when(studentRepository).deleteById(studentId);

        studentService.deleteStudent(studentId);

        verify(studentRepository, times(1)).deleteById(studentId);
    }
}

Explanation:

  • @ExtendWith(MockitoExtension.class): Tells JUnit to use the Mockito extension.
  • @Mock: Creates a mock instance of StudentRepository.
  • @InjectMocks: Injects the mocks into StudentService.
  • when, thenReturn, verify: Methods from Mockito to define behavior and verify interactions.

Writing Integration Tests

Integration tests verify the interaction between different components of the application. Spring Boot provides support for writing integration tests with the help of the @SpringBootTest annotation.

Step 4: Write Integration Tests

4.1 Create the Integration Test Class

Create a test class named StudentControllerIntegrationTest in the src/test/java/com/example/demo/controller package.

package com.example.demo.controller;

import com.example.demo.entity.Student;
import com.example.demo.repository.StudentRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Optional;

import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
public class StudentControllerIntegrationTest {

    @Autowired
    private MockMvc mockMvc

;

    @Autowired
    private StudentRepository studentRepository;

    private Student student;

    @BeforeEach
    public void setUp() {
        studentRepository.deleteAll();
        student = new Student();
        student.setName("John Doe");
        student.setEmail("[email protected]");
        studentRepository.save(student);
    }

    @Test
    public void testGetAllStudents() throws Exception {
        mockMvc.perform(get("/students")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0].name", is(student.getName())))
                .andExpect(jsonPath("$[0].email", is(student.getEmail())));
    }

    @Test
    public void testGetStudentById() throws Exception {
        mockMvc.perform(get("/students/{id}", student.getId())
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name", is(student.getName())))
                .andExpect(jsonPath("$.email", is(student.getEmail())));
    }

    @Test
    public void testCreateStudent() throws Exception {
        mockMvc.perform(post("/students")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\": \"Jane Doe\", \"email\": \"[email protected]\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name", is("Jane Doe")))
                .andExpect(jsonPath("$.email", is("[email protected]")));
    }

    @Test
    public void testDeleteStudent() throws Exception {
        mockMvc.perform(delete("/students/{id}", student.getId())
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());

        Optional<Student> deletedStudent = studentRepository.findById(student.getId());
        assertEquals(Optional.empty(), deletedStudent);
    }
}

Explanation:

  • @SpringBootTest: Tells Spring Boot to look for the main configuration class and start the application context.
  • @AutoConfigureMockMvc: Configures MockMvc for testing MVC controllers.
  • MockMvc: Mocks the servlet environment to test Spring MVC controllers.
  • @BeforeEach: Sets up the database state before each test.
  • mockMvc.perform, andExpect: Methods from MockMvc to send requests and verify responses.

Running the Tests

Run the tests using your IDE or from the command line with Maven:

./mvnw test

Conclusion

In this comprehensive guide, you have learned how to set up and run tests in a Spring Boot 3.2 application. We covered:

  • Setting up a Spring Boot project for testing.
  • Creating an entity, repository, service, and controller.
  • Writing unit tests for the service layer using JUnit and Mockito.
  • Writing integration tests for the controller layer using MockMvc and @SpringBootTest.

By following these steps, you can ensure your Spring Boot applications are well-tested and reliable.

Comments