Testing in Spring Boot: Unit Testing and Integration Testing

author : Sai K

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 {
    }
    

    Explanation:

    • JpaRepository: 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 getAllStudents() {
            return studentRepository.findAll();
        }
    
        public Optional 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.

    • 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 getAllStudents() {
            return studentService.getAllStudents();
        }
    
        @GetMapping("/{id}")
        public Optional 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("john.doe@example.com");
    
            Student student2 = new Student();
            student2.setName("Jane Doe");
            student2.setEmail("jane.doe@example.com");
    
            List students = Arrays.asList(student1, student2);
    
            when(studentRepository.findAll()).thenReturn(students);
    
            List 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("john.doe@example.com");
    
            when(studentRepository.findById(1L)).thenReturn(Optional.of(student));
    
            Optional result = studentService.getStudentById(1L);
    
            assertEquals("John Doe", result.get().getName());
            assertEquals("john.doe@example.com", result.get().getEmail());
            verify(studentRepository, times(1)).findById(1L);
        }
    
        @Test
        void testCreateStudent() {
            Student student = new Student();
            student.setName("John Doe");
            student.setEmail("john.doe@example.com");
    
            when(studentRepository.save(student)).thenReturn(student);
    
            Student result = studentService.createStudent(student);
    
            assertEquals("John Doe", result.getName());
            assertEquals("john.doe@example.com", 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("john.doe@example.com");
            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\": \"jane.doe@example.com\"}"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.name", is("Jane Doe")))
                    .andExpect(jsonPath("$.email", is("jane.doe@example.com")));
        }
    
        @Test
        public void testDeleteStudent() throws Exception {
            mockMvc.perform(delete("/students/{id}", student.getId())
                    .contentType(MediaType.APPLICATION_JSON))
                    .andExpect(status().isOk());
    
            Optional 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.

    Related Spring and Spring Boot Tutorials/Guides: