Unit testing is a software testing technique used to verify individual components of an application independently from the rest of the system. In Spring Boot, JUnit 5 is commonly used to write and execute test cases, while Mockito is used to create mock objects for dependencies such as repositories.
For example, a StudentService class may contain business logic for registering, retrieving, updating, and deleting students. Instead of connecting to a real MySQL database, we can mock the StudentRepository and test whether the service behaves correctly for valid inputs, missing students, and other scenarios.
Features of Unit Testing
-
Independent testing: Tests one class or method at a time.
-
JUnit 5: Provides annotations such as
@Test,@BeforeEach, and assertions. -
Mockito: Creates mock objects for dependent classes.
-
Dependency isolation: Tests service logic without requiring a database connection.
-
Assertions: Verify expected results using methods such as
assertEquals()andassertNotNull(). -
Exception testing: Checks whether methods throw the expected exceptions.
-
Repeatability: Allows tests to be executed repeatedly with predictable results.
-
Regression testing: Helps detect when code changes break existing functionality.
Hands-on Experiment: Creating a spring boot application to understand how individual Java components can be tested independently from the rest of the application.
The following steps are involved in developing this hands-on experiment.
Step 1: Generate the Project
Step 2: Project Structure
Step 3: pom.xml
Step 4: application.properties
Step 5: Create Entity Class
Step 6: Create Repository Interface
Step 7: Create Service Class
Step 8: Create Controller Class
Step 9: Main Application Class
Step 10: Run the Main Application Class
Step 1: Configure the Project on Spring Initializr
Go to start.spring.io and set:
- Project: Maven
- Language: Java
- Spring Boot version: 3.2.x
- Group: com.example
- Artifact: unittest
- Name: unittest
- Package name: com.example.unittest
- Packaging: Jar
- Java: 17
- Dependencies: Spring Web, Spring Data JPA, H2 Driver.
Click Generate to download the ZIP, then extract and import it into your IDE as a Maven project.
Step 2: Generated Project Structure

Step 3: Explore pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>student-unit-testing</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>student-unit-testing</name>
<description>Unit Testing a Student Service with JUnit 5 and Mockito</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Includes JUnit 5, Mockito, AssertJ, Hamcrest -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Step 4: application.properties
spring.application.name=student-unit-testing
server.port=8082
spring.datasource.url=jdbc:h2:mem:studentdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.h2.console.enabled=trueCode language: JavaScript (javascript)
Step 5: Create Entity Class
package com.example.unittest.model;
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;
private String course;
public Student() {
}
public Student(Long id, String name, String email, String course) {
this.id = id;
this.name = name;
this.email = email;
this.course = course;
}
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; }
public String getCourse() { return course; }
public void setCourse(String course) { this.course = course; }
}
Step 6: Create below Repository Interface
package com.example.unittest.repository;
import com.example.unittest.model.Student;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StudentRepository extends JpaRepository<Student, Long> {
boolean existsByEmail(String email);
}
Step 7: Create below Service class
package com.example.unittest.service;
import com.example.unittest.model.Student; // adjust to your package
import com.example.unittest.repository.StudentRepository; // adjust to your package
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
@Service
public class StudentService {
private final StudentRepository studentRepository;
public StudentService(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
public List<Student> getAllStudents() {
return studentRepository.findAll();
}
public Student getStudentById(Long id) {
return studentRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND, "Student not found with id: " + id));
}
public Student createStudent(Student student) {
if (studentRepository.existsByEmail(student.getEmail())) {
throw new ResponseStatusException(
HttpStatus.CONFLICT, "Student already exists with email: " + student.getEmail());
}
return studentRepository.save(student);
}
public Student updateStudent(Long id, Student details) {
Student existing = getStudentById(id);
existing.setName(details.getName());
existing.setEmail(details.getEmail());
existing.setCourse(details.getCourse());
return studentRepository.save(existing);
}
public void deleteStudent(Long id) {
if (!studentRepository.existsById(id)) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Student not found with id: " + id);
}
studentRepository.deleteById(id);
}
}Step 8: Create Controller class
package com.example.unittest.controller;
import com.example.unittest.model.Student;
import com.example.unittest.service.StudentService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/students")
public class StudentController {
private final StudentService studentService;
public StudentController(StudentService studentService) {
this.studentService = studentService;
}
@GetMapping
public List<Student> getAll() {
return studentService.getAllStudents();
}
@GetMapping("/{id}")
public Student getById(@PathVariable Long id) {
return studentService.getStudentById(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Student create(@RequestBody Student student) {
return studentService.createStudent(student);
}
@PutMapping("/{id}")
public Student update(@PathVariable Long id, @RequestBody Student student) {
return studentService.updateStudent(id, student);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
studentService.deleteStudent(id);
}
}
Step 9: Main Application
package com.example.unittest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StudentServiceApplication {
public static void main(String[] args) {
SpringApplication.run(StudentServiceApplication.class, args);
}
}
package com.example.studentservice.service;
import com.example.unittest.model.Student;
import com.example.unittest.repository.StudentRepository;
import com.example.unittest.service.StudentService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class StudentServiceTest {
@Mock
private StudentRepository studentRepository; // fake repository - no real database
@InjectMocks
private StudentService studentService; // class under test
private Student student;
@BeforeEach
void setUp() {
student = new Student(1L, "Raja", "raja@example.com", "Computer Science");
}
// ---------- getAllStudents ----------
@Test
@DisplayName("getAllStudents returns every student from the repository")
void getAllStudents_returnsList() {
Student second = new Student(2L, "Anu", "anu@example.com", "Mathematics");
when(studentRepository.findAll()).thenReturn(Arrays.asList(student, second));
List<Student> result = studentService.getAllStudents();
assertThat(result).hasSize(2).containsExactly(student, second);
verify(studentRepository).findAll();
}
@Test
@DisplayName("getAllStudents returns an empty list when no students exist")
void getAllStudents_returnsEmptyList() {
when(studentRepository.findAll()).thenReturn(List.of());
assertThat(studentService.getAllStudents()).isEmpty();
}
// ---------- getStudentById ----------
@Test
@DisplayName("getStudentById returns the student when the id exists")
void getStudentById_found() {
when(studentRepository.findById(1L)).thenReturn(Optional.of(student));
Student result = studentService.getStudentById(1L);
assertEquals("Raja", result.getName());
assertEquals("raja@example.com", result.getEmail());
}
@Test
@DisplayName("getStudentById throws 404 when the id is missing")
void getStudentById_notFound() {
when(studentRepository.findById(99L)).thenReturn(Optional.empty());
ResponseStatusException ex = assertThrows(
ResponseStatusException.class,
() -> studentService.getStudentById(99L));
assertEquals(HttpStatus.NOT_FOUND, ex.getStatusCode());
assertEquals("Student not found with id: 99", ex.getReason());
}
// ---------- createStudent ----------
@Test
@DisplayName("createStudent saves and returns the student when the email is new")
void createStudent_success() {
when(studentRepository.existsByEmail("raja@example.com")).thenReturn(false);
when(studentRepository.save(student)).thenReturn(student);
Student saved = studentService.createStudent(student);
assertThat(saved).isNotNull();
assertThat(saved.getId()).isEqualTo(1L);
verify(studentRepository).save(student);
}
@Test
@DisplayName("createStudent throws 409 and never saves when the email exists")
void createStudent_duplicateEmail() {
when(studentRepository.existsByEmail("raja@example.com")).thenReturn(true);
ResponseStatusException ex = assertThrows(
ResponseStatusException.class,
() -> studentService.createStudent(student));
assertEquals(HttpStatus.CONFLICT, ex.getStatusCode());
verify(studentRepository, never()).save(any(Student.class));
}
// ---------- updateStudent ----------
@Test
@DisplayName("updateStudent copies the new values onto the existing student and saves it")
void updateStudent_success() {
Student changes = new Student(null, "Raja Updated", "raja.new@example.com", "Data Science");
when(studentRepository.findById(1L)).thenReturn(Optional.of(student));
when(studentRepository.save(any(Student.class))).thenAnswer(inv -> inv.getArgument(0));
Student result = studentService.updateStudent(1L, changes);
assertEquals("Raja Updated", result.getName());
assertEquals("raja.new@example.com", result.getEmail());
assertEquals("Data Science", result.getCourse());
ArgumentCaptor<Student> captor = ArgumentCaptor.forClass(Student.class);
verify(studentRepository).save(captor.capture());
assertEquals(1L, captor.getValue().getId()); // id must stay the same
}
@Test
@DisplayName("updateStudent throws 404 when the id is missing")
void updateStudent_notFound() {
when(studentRepository.findById(99L)).thenReturn(Optional.empty());
ResponseStatusException ex = assertThrows(
ResponseStatusException.class,
() -> studentService.updateStudent(99L, student));
assertEquals(HttpStatus.NOT_FOUND, ex.getStatusCode());
verify(studentRepository, never()).save(any(Student.class));
}
// ---------- deleteStudent ----------
@Test
@DisplayName("deleteStudent deletes the student when the id exists")
void deleteStudent_success() {
when(studentRepository.existsById(1L)).thenReturn(true);
studentService.deleteStudent(1L);
verify(studentRepository).deleteById(1L);
}
@Test
@DisplayName("deleteStudent throws 404 and never deletes when the id is missing")
void deleteStudent_notFound() {
when(studentRepository.existsById(99L)).thenReturn(false);
ResponseStatusException ex = assertThrows(
ResponseStatusException.class,
() -> studentService.deleteStudent(99L));
assertEquals(HttpStatus.NOT_FOUND, ex.getStatusCode());
verify(studentRepository, never()).deleteById(any());
}
}Step 10: Run the StudentServiceTest.java

