Chapter 3: Unit Testing with Spring Boot


This chapter provides a comprehensive understanding of unit testing principles, focusing on how to apply these concepts using Spring Boot, JUnit, and Mockito. It includes practical examples, best practices, a hands-on lab, and a quiz to reinforce learning.


Objective

By the end of this chapter, you will:

  • Understand the importance of unit testing in software development.
  • Learn how to write unit tests for Spring Boot applications using JUnit and Mockito.
  • Master techniques for isolating units of code and mocking dependencies.
  • Apply best practices for writing maintainable and effective unit tests.

3.1 Introduction to Unit Testing

Unit testing is the foundation of software testing, focusing on verifying the smallest testable parts of an application—typically methods or classes.

  • Definition: Unit testing involves testing individual components in isolation to ensure they work as expected, independent of other parts of the system.
  • Purpose:
    • Catch bugs early in the development cycle, reducing debugging time later.
    • Ensure each unit of code performs correctly before integration.
    • Provide a safety net for refactoring, preventing regressions.
  • Key Characteristics:
    • Isolation: Units are tested independently from external systems or dependencies.
    • Automation: Tests are automated, allowing repeated execution without manual effort.
    • Speed: Unit tests execute quickly, providing rapid feedback to developers.

Think of unit testing as checking each brick before building a wall—ensuring every piece is solid and reliable.


3.2 Tools for Unit Testing in Spring Boot

To perform unit testing in Spring Boot, we rely on two powerful tools:

  • JUnit: A popular testing framework for Java that provides annotations (e.g., @Test) and assertions (e.g., assertEquals) to write and run tests.
  • Mockito: A mocking framework that lets you create mock objects to simulate the behavior of dependencies, ensuring tests focus on the unit under test.

These tools integrate seamlessly with Spring Boot, making them ideal for testing Spring components like services and controllers.


3.3 Writing Unit Tests for Spring Boot Services

Services in Spring Boot often contain business logic and depend on repositories or other services. Unit testing these services requires isolating them from their dependencies using mocks.

3.3.1 Example Scenario

Consider a simple BookService that manages books:

@Service
public class BookService {
    private final BookRepository bookRepository;

    @Autowired
    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public Book createBook(String title, String author) {
        Book book = new Book(title, author);
        return bookRepository.save(book);
    }

    public List<Book> getAllBooks() {
        return bookRepository.findAll();
    }
}

3.3.2 Writing a Unit Test for createBook

To test the createBook method, we:

  • Mock the BookRepository to control its behavior.
  • Verify that save is called with the correct book object.
  • Check that the method returns the expected book.

Test Code:

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

import org.junit.jupiter.api.Test;

public class BookServiceTest {
    @Test
    public void testCreateBook() {
        // Arrange
        BookRepository mockRepo = mock(BookRepository.class);
        BookService service = new BookService(mockRepo);
        Book bookToSave = new Book("Test Title", "Test Author");
        when(mockRepo.save(any(Book.class))).thenReturn(bookToSave);

        // Act
        Book result = service.createBook("Test Title", "Test Author");

        // Assert
        assertEquals("Test Title", result.getTitle());
        assertEquals("Test Author", result.getAuthor());
        verify(mockRepo).save(any(Book.class));
    }
}

Explanation:

  • Arrange: Set up the mock BookRepository and instantiate BookService with it. Use when to define the mock’s behavior.
  • Act: Call the createBook method with test data.
  • Assert: Check the returned book’s properties and verify the mock interaction.

3.3.3 Writing a Unit Test for getAllBooks

For getAllBooks, we:

  • Mock the repository to return a predefined list of books.
  • Verify that findAll is called.
  • Check that the method returns the correct list.

Test Code:

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;

public class BookServiceTest {
    @Test
    public void testGetAllBooks() {
        // Arrange
        BookRepository mockRepo = mock(BookRepository.class);
        List<Book> books = Arrays.asList(new Book("Book1", "Author1"), new Book("Book2", "Author2"));
        when(mockRepo.findAll()).thenReturn(books);
        BookService service = new BookService(mockRepo);

        // Act
        List<Book> result = service.getAllBooks();

        // Assert
        assertEquals(2, result.size());
        assertEquals("Book1", result.get(0).getTitle());
        verify(mockRepo).findAll();
    }
}

3.4 Writing Unit Tests for Spring Boot Controllers

Controllers handle HTTP requests and responses. Unit testing them involves verifying they process input correctly and return the expected output, while mocking their dependencies.

3.4.1 Example Scenario

Consider a BookController that exposes endpoints for books:

@RestController
@RequestMapping("/books")
public class BookController {
    private final BookService bookService;

    @Autowired
    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @PostMapping
    public ResponseEntity<Book> createBook(@RequestBody Book book) {
        Book createdBook = bookService.createBook(book.getTitle(), book.getAuthor());
        return ResponseEntity.ok(createdBook);
    }

    @GetMapping
    public ResponseEntity<List<Book>> getAllBooks() {
        return ResponseEntity.ok(bookService.getAllBooks());
    }
}

3.4.2 Writing a Unit Test for createBook

To test the createBook endpoint:

  • Mock the BookService.
  • Simulate a POST request using MockMvc.
  • Verify the response status and body.

Test Code:

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(BookController.class)
public class BookControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookService bookService;

    @Test
    public void testCreateBook() throws Exception {
        // Arrange
        Book book = new Book("Test Title", "Test Author");
        when(bookService.createBook(anyString(), anyString())).thenReturn(book);

        // Act & Assert
        mockMvc.perform(post("/books")
                .contentType(MediaType.APPLICATION_JSON)
                .content(new ObjectMapper().writeValueAsString(book)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.title").value("Test Title"))
                .andExpect(jsonPath("$.author").value("Test Author"));
    }
}

Explanation:

  • @WebMvcTest: Loads only the web layer (controllers) for testing.
  • MockMvc: Simulates HTTP requests to the controller.
  • @MockBean: Injects a mock BookService into the controller.

3.4.3 Writing a Unit Test for getAllBooks

For getAllBooks:

  • Mock the service to return a list of books.
  • Simulate a GET request.
  • Verify the response contains the expected books.

Test Code:

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(BookController.class)
public class BookControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookService bookService;

    @Test
    public void testGetAllBooks() throws Exception {
        // Arrange
        List<Book> books = Arrays.asList(new Book("Book1", "Author1"), new Book("Book2", "Author2"));
        when(bookService.getAllBooks()).thenReturn(books);

        // Act & Assert
        mockMvc.perform(get("/books"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0].title").value("Book1"))
                .andExpect(jsonPath("$[1].title").value("Book2"));
    }
}

3.5 Best Practices for Unit Testing

To ensure your unit tests are effective and maintainable, follow these guidelines:

  • Isolate Units: Use mocks to test each component independently of its dependencies.
  • Test One Thing at a Time: Each test should verify a single behavior or scenario.
  • Use Meaningful Names: Name tests descriptively (e.g., testCreateBook_Success) to clarify their purpose.
  • Keep Tests Fast: Avoid slow operations like database calls; mocks help achieve this.
  • Avoid Test Dependencies: Ensure tests are independent and can run in any order.
  • Use Assertions Wisely: Focus assertions on the specific behavior being tested.

3.6 Lab: Hands-On Unit Testing with Spring Boot

Apply your knowledge in this practical lab.

Lab Objective

Write unit tests for a provided Spring Boot REST API using JUnit and Mockito.

Lab Setup

  • Provided: A sample Spring Boot application with a BookService and BookController similar to the examples above.
  • Tools: Maven or Gradle for dependency management, with JUnit and Mockito included.

Lab Tasks

  1. Write Unit Tests for the Service Layer:

    • Test createBook to ensure it saves a book and returns it correctly.
    • Test getAllBooks to verify it retrieves the list of books from the repository.
  2. Write Unit Tests for the Controller Layer:

    • Test the createBook endpoint to confirm it handles POST requests and returns the created book.
    • Test the getAllBooks endpoint to ensure it returns the list of books with a 200 status.
  3. Verify Mock Interactions:

    • In service tests, ensure save and findAll are called on the repository as expected.
    • In controller tests, confirm the service methods are invoked correctly.

Lab Deliverables

  • Submit your test classes with all unit tests.
  • Include comments in your code explaining the purpose of each test.

3.7 Quiz: Reinforcing Key Concepts

Test your understanding with this quiz:

  1. What is the primary goal of unit testing?

    • a) To test the entire application
    • b) To verify individual components in isolation
    • c) To check integration between components
    • d) To validate user interfaces
  2. Which tool is used for mocking dependencies in Java?

    • a) JUnit
    • b) Mockito
    • c) Spring Boot Test
    • d) Rest Assured
  3. In a unit test for a Spring Boot controller, what does @WebMvcTest do?

    • a) Loads the full application context
    • b) Loads only the web layer for testing
    • c) Mocks the database
    • d) Simulates HTTP requests
  4. True or False: Unit tests should be slow to ensure thoroughness.

    • a) True
    • b) False
  5. What is the purpose of the verify() method in Mockito?

    • a) To check if a method was called on a mock object
    • b) To assert the result of a test
    • c) To create a mock object
    • d) To run the test

Answers:

  1. b
  2. b
  3. b
  4. b (Unit tests should be fast)
  5. a

3.8 Summary

In this chapter, you’ve learned:

  • The critical role of unit testing in ensuring code quality and reliability.
  • How to use JUnit and Mockito to write unit tests for Spring Boot services and controllers.
  • Techniques for isolating units and mocking dependencies to focus tests on specific behaviors.
  • Best practices for creating maintainable, efficient, and effective unit tests.

These skills are essential for building robust Spring Boot applications and provide a strong foundation for exploring advanced testing techniques.


This chapter equips you with both the theoretical knowledge and practical skills to perform unit testing effectively in Spring Boot. You’re now prepared to tackle real-world testing challenges!


By Wahid Hamdi