Chapter 9.3☕ 16 min read

Unit Testing with JUnit 5 & Mockito

Unit testing is the foundation of software quality. A unit test checks a single Java class in complete isolation. If your <code>UserService</code> talks to a <code>Database</code>, a unit test fakes the database so you only test the logic inside <code>UserService</code>. We use <strong>JUnit 5</strong> and <strong>Mockito</strong> for this.

01The Concept: Mocking Dependencies

The Chef Tasting the Sauce Analogy:

Before a Hyderabadi chef serves Biryani to 1000 people at a wedding, they don't cook 1000 plates to see if the recipe is right. They cook a small sample of the masala in a test kitchen (Unit Test). They use fake, cheap ingredients (Mocks) instead of the real expensive saffron, just to check if the recipe logic is correct.

In Java, Mockito creates fake objects (Mocks) of your repositories. You tell the mock exactly what to return, so you can test your Service's logic without needing a real database.

02Technical Explanation
  1. JUnit 5 (org.junit.jupiter): The modern testing framework. Uses @Test and @BeforeEach.
  2. @ExtendWith(MockitoExtension.class): Tells JUnit to enable Mockito for this test class.
  3. @Mock: Creates a fake, empty object of a dependency (e.g., a fake Repository).
  4. @InjectMocks: Creates a real instance of the class you are testing (e.g., Service) and automatically injects the @Mock objects into it.
  5. when().thenReturn(): Trains the mock. "When someone calls findById(1), return a fake Book."
03Full Working Code: Testing a Service
package com.devinhyderabad;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

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

// 1. Enable Mockito
@ExtendWith(MockitoExtension.class)
public class BookServiceTest {

// 2. Create a fake BookRepository (It won't touch a real DB)
@Mock
private BookRepository bookRepository;

// 3. Create a real BookService and inject the fake repo into it
@InjectMocks
private BookService bookService;

@Test
public void testGetBookTitle_ReturnsUpperCase() {
// 4. Arrange: Train the mock
Book fakeBook = new Book(1L, "spring boot", "Deva");
when(bookRepository.findById(1L)).thenReturn(Optional.of(fakeBook));

// 5. Act: Call the real service method
String result = bookService.getBookTitleUpperCase(1L);

// 6. Assert: Check if the logic worked
assertEquals("SPRING BOOT", result);
}
}

Note: We assume BookService has a method getBookTitleUpperCase that fetches the book and uppercases the title.

04Why It Matters

Interview Question: "What is the difference between a Unit Test and an Integration Test in Spring Boot?"

Answer: A unit test (using @ExtendWith(MockitoExtension.class)) tests a single class in isolation. It does not start the Spring Application Context and uses mocked dependencies. It is extremely fast. An integration test (using @SpringBootTest) starts the real Spring context, connects to a real (or test) database, and tests how multiple components work together.

05Interview Note

Enterprise Note: Follow the Test Pyramid. 80% of your tests should be fast Unit Tests (Mockito). Only 20% should be slow Integration Tests. If you write only integration tests, your CI/CD pipeline will take an hour to run.

Key Takeaways

  • ✅ @ExtendWith(MockitoExtension.class) enables Mockito in JUnit 5
  • ✅ @Mock creates fake objects; @InjectMocks injects them into the class under test
  • ✅ when().thenReturn() trains the mock to return specific values
  • ✅ Follow the Test Pyramid: 80% unit tests, 20% integration tests