How to Write Unit Tests in Spring Boot - Best Practices and Examples

Writing unit tests in Spring Boot involves using various testing frameworks and utilities provided by Spring Boot and JUnit.

The main purpose of writing unit tests is to ensure that each small, self-contained part of the program, such as methods or functions, behaves as expected and produces the correct output for a given set of inputs.

Here's a step-by-step guide on how to write unit tests in Spring Boot:

  1. Add Dependencies: Make sure you have the necessary dependencies for testing in your pom.xml or build.gradle file. Spring Boot typically includes JUnit and Spring Test by default, but you can add additional testing libraries like Mockito, AssertJ, etc., as per your requirements.

  2. In this example, we will use JUnit and Mockito.

    Dependencies required for Gradle projects:

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation group: 'org.mockito', name: 'mockito-core', version: '5.4.0'

    Dependencies required for Maven projects:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>5.4.0</version>
        <scope>test</scope>
    </dependency>

  3. Create the Test Class: Here's an example of unit testing, created using JUnit and Mockito, for the StudentServiceImpl class. The unit test is written to test the individual methods: addStudent, updateStudent, deleteStudent, getStudentById, and getAllStudents. We'll mock the StudentRepository and use Mockito to isolate the class being tested:

  4. import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.junit.jupiter.api.Assertions.assertNotNull;
    import static org.junit.jupiter.api.Assertions.assertThrows;
    import static org.mockito.ArgumentMatchers.any;
    import static org.mockito.Mockito.times;
    import static org.mockito.Mockito.verify;
    import static org.mockito.Mockito.when;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Optional;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.MockitoAnnotations;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageImpl;
    import org.springframework.data.domain.Pageable;
    import com.example.exception.DataMissingException;
    import com.example.exception.ResourceNotFoundException;
    import com.example.student.entity.Student;
    import com.example.student.model.StudentRequest;
    import com.example.student.model.StudentResponse;
    import com.example.student.repository.StudentRepository;
    import com.example.student.service.StudentService;
    import com.example.student.service.impl.StudentServiceImpl;
    
    @SpringBootTest
    public class StudentServiceTest {
    
      @Mock
      private StudentRepository studentRepository;
    
      @InjectMocks
      private StudentService studentService = new StudentServiceImpl();
      
      @BeforeEach
      public void setUp() {
          // Initializing Mockito manually
          MockitoAnnotations.openMocks(this);
      }
    
     
      @Test
      public void testAddStudent_Success() {
          StudentRequest studentRequest = new StudentRequest();
          studentRequest.setFirstName("John");
          studentRequest.setLastName("Abc");
          studentRequest.setEmail("john.abc@example.com");
    
          Student student = new Student();
          student.setId(1L);
          student.setFirstName("John");
          student.setLastName("Abc");
          student.setEmail("john.abc@example.com");
    
          when(studentRepository.save(any(Student.class))).thenReturn(student);
    
          StudentResponse result = studentService.addStudent(studentRequest);
    
          assertNotNull(result);
          assertEquals(1L, result.getStudentId());
          assertEquals("John", result.getFirstName());
          assertEquals("Abc", result.getLastName());
          assertEquals("john.abc@example.com", result.getEmail());
      }
      
      @Test
      public void testAddStudent_DataMissingException() {
          StudentRequest studentRequest = new StudentRequest();
    
          assertThrows(DataMissingException.class, () -> studentService.addStudent(studentRequest));
      }
      
      @Test
      public void testUpdateStudent_Success() {
          Long studentId = 1L;
    
          StudentRequest studentRequest = new StudentRequest();
          studentRequest.setStudentId(studentId);
          studentRequest.setFirstName("John");
          studentRequest.setLastName("Abc");
          
          Student existingStudent = new Student();
          existingStudent.setId(studentId);
          existingStudent.setFirstName("John");
    
          when(studentRepository.findById(studentId)).thenReturn(Optional.of(existingStudent));
          when(studentRepository.save(any(Student.class))).thenReturn(existingStudent);
    
          StudentResponse result = studentService.updateStudent(studentRequest);
    
          assertNotNull(result);
          assertEquals(studentId, result.getStudentId());
          assertEquals("John", result.getFirstName());
          assertEquals("Abc", result.getLastName());
      }
      
      @Test
      public void testUpdateStudent_ResourceNotFoundException() {
          Long studentId = 1L;
          StudentRequest studentRequest = new StudentRequest();
          studentRequest.setStudentId(studentId);
    
          when(studentRepository.findById(studentId)).thenReturn(Optional.empty());
    
          assertThrows(ResourceNotFoundException.class, () -> studentService.updateStudent(studentRequest));
      }
      
      @Test
      public void testDeleteStudent_Success() {
          Long studentId = 1L;
    
          Student existingStudent = new Student();
          existingStudent.setId(studentId);
    
          when(studentRepository.findById(studentId)).thenReturn(Optional.of(existingStudent));
    
          studentService.deleteStudent(studentId);
    
          verify(studentRepository, times(1)).deleteById(studentId);
      }
      
      @Test
      public void testDeleteStudent_ResourceNotFoundException() {
          Long studentId = 1L;
    
          when(studentRepository.findById(studentId)).thenReturn(Optional.empty());
    
          assertThrows(ResourceNotFoundException.class, () -> studentService.deleteStudent(studentId));
      }
    
      @Test
      public void testGetStudentById_Success() {
          Long studentId = 1L;
    
          Student existingStudent = new Student();
          existingStudent.setId(studentId);
          existingStudent.setFirstName("John");
          existingStudent.setLastName("Abc");
          existingStudent.setEmail("john.abc@example.com");
    
          when(studentRepository.findById(studentId)).thenReturn(Optional.of(existingStudent));
    
          StudentResponse result = studentService.getStudentById(studentId);
    
          assertNotNull(result);
          assertEquals(studentId, result.getStudentId());
          assertEquals("John", result.getFirstName());
          assertEquals("Abc", result.getLastName());
          assertEquals("john.abc@example.com", result.getEmail());
      }
    
      @Test
      public void testGetStudentById_ResourceNotFoundException() {
          Long studentId = 1L;
    
          when(studentRepository.findById(studentId)).thenReturn(Optional.empty());
    
          assertThrows(ResourceNotFoundException.class, () -> studentService.getStudentById(studentId));
      }
    
      @Test
      public void testGetAllStudents_Success() {
          List<Student> students = new ArrayList<>();
          
          Student existingStudent1 = new Student();
          existingStudent1.setId(1L);
          existingStudent1.setFirstName("John");
          existingStudent1.setLastName("Abc");
          existingStudent1.setEmail("john.abc@example.com");
          
          students.add(existingStudent1);
          
          Student existingStudent2 = new Student();
          existingStudent2.setId(1L);
          existingStudent2.setFirstName("Jane");
          existingStudent2.setLastName("Xyz");
          existingStudent2.setEmail("jane.xyz@example.com");
          
          students.add(existingStudent2);
    
          Pageable pageable = Pageable.unpaged();
          Page<Student> studentsPage = new PageImpl<>(students);
    
          when(studentRepository.findAll(pageable)).thenReturn(studentsPage);
    
          Page<StudentResponse> resultPage = studentService.getAllStudents(pageable);
          List<StudentResponse> resultList = resultPage.getContent();
    
          assertNotNull(resultList);
          assertEquals(2, resultList.size());
          assertEquals("John", resultList.get(0).getFirstName());
          assertEquals("Abc", resultList.get(0).getLastName());
          assertEquals("jane.xyz@example.com", resultList.get(1).getEmail());
      }
    }

    In the above example, the @MockBean annotation is used to create a mock of a Spring bean to be used in the test. The @InjectMocks annotation is used to inject the mock objects into the target class being tested. The @SpringBootTest is used to load the complete Spring application context, allowing you to test the beans in a real Spring environment.