Unit Testing

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

  1. Independent testing: Tests one class or method at a time.

  2. JUnit 5: Provides annotations such as @Test, @BeforeEach, and assertions.

  3. Mockito: Creates mock objects for dependent classes.

  4. Dependency isolation: Tests service logic without requiring a database connection.

  5. Assertions: Verify expected results using methods such as assertEquals() and assertNotNull().

  6. Exception testing: Checks whether methods throw the expected exceptions.

  7. Repeatability: Allows tests to be executed repeatedly with predictable results.

  8. 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

Scroll to Top