Mockito

  1. Mockito is a Java library that creates fake (mock) objects so a class can be tested on its own.
  2. It is used to isolate business logic: the service is tested without a real database or repository.
  3. A mock returns whatever you tell it to, so tests are fast, repeatable and need no MySQL.
  4. You can also verify how the service used the mock, such as how many times it called save().

Features

  • Create mocks of classes and interfaces with @Mock.
  • Inject mocks into the class under test with @InjectMocks.
  • Define fake behaviour with when(...).thenReturn(...) and thenThrow(...).
  • Verify interactions with verify(), times(), never().
  • Capture the arguments passed to a mock with ArgumentCaptor.
  • Works with JUnit 5 through @ExtendWith(MockitoExtension.class).

Hands-on Experiment: Creating a spring boot application to understand how dependencies can be mocked to isolate business logic during testing.

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: mockitodemo
  • Name: mockitodemo
  • Package name: com.example.mockitodemo
  • Packaging: Jar
  • Java: 17
  • Dependencies: Spring Web, Spring Data JPA, MySQL 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.2.5</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>student-mockito-demo</artifactId>
    <version>1.0.0</version>
    <name>student-mockito-demo</name>
    <description>Testing Student Service with a mocked Student Repository using 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.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- Includes JUnit 5, Mockito and AssertJ -->
        <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-mockito-demo
server.port=8082
# MySQL (database is created automatically if it does not exist) - change username/password as needed
spring.datasource.url=jdbc:mysql://localhost:3306/studentdb?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=password

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=trueCode language: PHP (php)

Step 5: Create Entity Class

package com.example.mockitodemo.entity;

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

@Entity
@Table(name = "students")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;
    private String department;

    public Student() {
    }

    public Student(Long id, String name, String email, String department) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.department = department;
    }

    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 getDepartment() { return department; }
    public void setDepartment(String department) { this.department = department; }
}

Step 6: Create below Repository Interface

package com.example.mockitodemo.repository;

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

import java.util.Optional;

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

    Optional<Student> findByEmail(String email);
}

Step 7: Create below Service class

package com.example.mockitodemo.service;

import com.example.mockitodemo.entity.Student;
import com.example.mockitodemo.repository.StudentRepository;
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;

    // Constructor injection - this is what lets Mockito inject a mock repository in tests
    public StudentService(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

    public Student addStudent(Student student) {
        if (studentRepository.findByEmail(student.getEmail()).isPresent()) {
            throw new ResponseStatusException(HttpStatus.CONFLICT,
                    "Email already registered: " + student.getEmail());
        }
        return studentRepository.save(student);
    }

    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 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.mockitodemo.controller;

import com.example.mockitodemo.entity.Student;
import com.example.mockitodemo.service.StudentService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;

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

    private final StudentService studentService;

    public StudentController(StudentService studentService) {
        this.studentService = studentService;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Student addStudent(@RequestBody Student student) {
        return studentService.addStudent(student);
    }

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

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

    @DeleteMapping("/{id}")
    public String deleteStudent(@PathVariable Long id) {
        studentService.deleteStudent(id);
        return "Student deleted with id: " + id;
    }
}

Step 9: Main Application

package com.example.mockitodemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MockitoDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(MockitoDemoApplication.class, args);
    }
}
package com.example.mockitodemo.service;

import com.example.mockitodemo.entity.Student;
import com.example.mockitodemo.repository.StudentRepository;
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.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;

// Pure unit test: no Spring context, no database. Only StudentService is real.
@ExtendWith(MockitoExtension.class)
class StudentServiceTest {

    @Mock
    private StudentRepository studentRepository;      // fake repository

    @InjectMocks
    private StudentService studentService;            // real service, mock injected into it

    @Test
    @DisplayName("addStudent saves a student when the email is new")
    void addStudent_success() {
        Student input = new Student(null, "Ravi", "ravi@mail.com", "CSE");
        Student saved = new Student(1L, "Ravi", "ravi@mail.com", "CSE");

        when(studentRepository.findByEmail("ravi@mail.com")).thenReturn(Optional.empty());
        when(studentRepository.save(any(Student.class))).thenReturn(saved);

        Student result = studentService.addStudent(input);

        assertEquals(1L, result.getId());
        assertEquals("Ravi", result.getName());
        verify(studentRepository, times(1)).save(input);
    }

    @Test
    @DisplayName("addStudent passes the correct object to the repository (ArgumentCaptor)")
    void addStudent_capturesSavedObject() {
        Student input = new Student(null, "Sita", "sita@mail.com", "ECE");

        when(studentRepository.findByEmail("sita@mail.com")).thenReturn(Optional.empty());
        when(studentRepository.save(any(Student.class))).thenAnswer(inv -> inv.getArgument(0));

        studentService.addStudent(input);

        ArgumentCaptor<Student> captor = ArgumentCaptor.forClass(Student.class);
        verify(studentRepository).save(captor.capture());
        assertEquals("ECE", captor.getValue().getDepartment());
    }

    @Test
    @DisplayName("addStudent throws 409 and never saves when the email already exists")
    void addStudent_duplicateEmail() {
        Student input = new Student(null, "Ravi", "ravi@mail.com", "CSE");
        Student existing = new Student(5L, "Ravi K", "ravi@mail.com", "IT");

        when(studentRepository.findByEmail("ravi@mail.com")).thenReturn(Optional.of(existing));

        ResponseStatusException ex = assertThrows(ResponseStatusException.class,
                () -> studentService.addStudent(input));

        assertEquals(HttpStatus.CONFLICT, ex.getStatusCode());
        verify(studentRepository, never()).save(any(Student.class));
    }

    @Test
    @DisplayName("getAllStudents returns the list given by the repository")
    void getAllStudents() {
        List<Student> students = List.of(
                new Student(1L, "Ravi", "ravi@mail.com", "CSE"),
                new Student(2L, "Sita", "sita@mail.com", "ECE"));

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

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

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

    @Test
    @DisplayName("getStudentById returns the student when found")
    void getStudentById_found() {
        Student student = new Student(1L, "Ravi", "ravi@mail.com", "CSE");

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

        Student result = studentService.getStudentById(1L);

        assertEquals("Ravi", result.getName());
    }

    @Test
    @DisplayName("getStudentById throws 404 when not found")
    void getStudentById_notFound() {
        when(studentRepository.findById(99L)).thenReturn(Optional.empty());

        ResponseStatusException ex = assertThrows(ResponseStatusException.class,
                () -> studentService.getStudentById(99L));

        assertEquals(HttpStatus.NOT_FOUND, ex.getStatusCode());
    }

    @Test
    @DisplayName("deleteStudent deletes when the student exists")
    void deleteStudent_success() {
        when(studentRepository.existsById(1L)).thenReturn(true);

        studentService.deleteStudent(1L);

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

    @Test
    @DisplayName("deleteStudent throws 404 and never deletes when student is missing")
    void deleteStudent_notFound() {
        when(studentRepository.existsById(99L)).thenReturn(false);

        assertThrows(ResponseStatusException.class, () -> studentService.deleteStudent(99L));

        verify(studentRepository, never()).deleteById(anyLong());
    }
}

Step 10: Run the StudentServiceApplication

Scroll to Top