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.