DEV Community

DhareshP
DhareshP

Posted on

Testing in Spring Boot with JUnit and Mockito: A Beginner’s Guide

Writing automated tests is one of the best investments you can make in your codebase. It helps catch bugs early, documents your intended behavior, and lets you refactor safely. In this guide, we’ll walk through the very basics and build up to using JUnit 5, Mockito, and MockMvc to test controllers and services in a Spring Boot application—complete with all the key annotations, patterns, and examples a beginner needs.



📚 Table of Contents

  1. Why Test?
  2. JUnit 5 Basics
  3. Mockito Basics
  4. Testing a Simple Controller with MockMvc
  5. Testing a Service Class with Mockito
  6. Annotation Cheat‑Sheet (Copyable Block)
  7. Putting It All Together: Mini‑Project Walkthrough
  8. Best Practices & Tips

1. Why Test?

  • Early bug detection: Tests run in milliseconds, so you find mistakes before they reach production.
  • Safety net for refactoring: Change code with confidence—if a test breaks, you know exactly what’s broken.
  • Living documentation: Tests show how your code is supposed to work more clearly than comments.

💡 Even a small app benefits from a handful of tests on its most important parts!


2. JUnit 5 Basics

JUnit is the standard Java testing framework. JUnit 5 (“Jupiter”) gives you:

  • Annotations

    • @Test: mark a method as a test case
    • @BeforeEach / @AfterEach: run setup/cleanup before and after each test
    • @BeforeAll / @AfterAll: run once before/after all tests (static methods)
    • @DisplayName: give a human‑readable name to a test
  • Assertions

    • Basic: assertEquals(expected, actual), assertTrue(condition), assertThrows(...)
    • Fluent (via AssertJ): assertThat(actual).isEqualTo(expected)
  • Lifecycle: JUnit discovers and runs your tests, applies setup/teardown annotations, and reports results.

Example: Testing a Plain Java Method

import org.junit.jupiter.api.*;

class CalculatorTest {
    private Calculator calc;

    @BeforeAll
    static void initAll() { System.out.println("Starting Calculator Tests"); }

    @BeforeEach
    void init() { calc = new Calculator(); }

    @Test
    @DisplayName("2 + 3 = 5")
    void addsTwoNumbers() {
        Assertions.assertEquals(5, calc.add(2, 3), "2 + 3 should equal 5");
    }

    @Test
    void throwsOnDivideByZero() {
        Assertions.assertThrows(ArithmeticException.class, () -> calc.divide(1, 0));
    }

    @AfterEach
    void tearDown() { /* cleanup if needed */ }

    @AfterAll
    static void tearDownAll() { System.out.println("All tests done"); }
}
Enter fullscreen mode Exit fullscreen mode

3. Mockito Basics

When your code depends on external collaborators—such as databases, web clients, or other services—you want to isolate the class under test. Mocking fakes out those collaborators so you can:

  • Control behavior: decide what a method should return (or throw).
  • Verify interactions: assert that a collaborator was called with specific arguments.
  • Avoid side effects: no real database, no network calls.

Key Annotations

  • @ExtendWith(MockitoExtension.class) — enable Mockito support in JUnit 5
  • @Mock — create a mock instance of an interface or class
  • @InjectMocks — instantiate the class under test and inject its @Mock fields
  • @Spy — partial mock: real methods run unless stubbed
  • @Captor — capture method arguments for later assertions
  • @MockBean — (Spring Boot only) replace a bean in the application context with a mock

Example: Mocking a Repository in a Service Test

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock
    UserRepository repo;             // fake repository

    @InjectMocks
    UserService service;             // real service with fake repo injected

    @Test
    void getUserName_userExists_returnsName() {
        when(repo.findById(1L))
            .thenReturn(Optional.of(new User(1L, "Alice")));

        String name = service.getUserName(1L);

        assertEquals("Alice", name);
        verify(repo).findById(1L);  // ensure method was called
    }

    @Test
    void getUserName_userNotFound_returnsUnknown() {
        when(repo.findById(2L)).thenReturn(Optional.empty());
        assertEquals("Unknown", service.getUserName(2L));
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Testing a Simple Controller with MockMvc

Spring’s MockMvc lets you test @RestController methods without starting a real server. You simulate HTTP requests and inspect responses.

4.1 Controller Code

@RestController
@RequestMapping("/test")
public class SampleController {

    @GetMapping
    public String sampleMethod() {
        return "Application Deployed on AWS EC2";
    }
}
Enter fullscreen mode Exit fullscreen mode

4.2 Test Code (JUnit + MockMvc)

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;

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

@WebMvcTest(SampleController.class)
class SampleControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void sampleMethod_returnsExpectedString() throws Exception {
        mockMvc.perform(get("/test"))
               .andExpect(status().isOk())
               .andExpect(content().string("Application Deployed on AWS EC2"));
    }
}
Enter fullscreen mode Exit fullscreen mode

Common Pitfall:
Don’t annotate @WebMvcTest with your test class—you must specify the controller class you’re testing.


5. Testing a Service Class with Mockito

Service classes contain business logic. If they depend on repositories or other services, you mock those collaborators.

5.1 Service Code

@Service
public class SampleService {
    private final SampleRepository repo;

    public SampleService(SampleRepository repo) {
        this.repo = repo;
    }

    public String getData() {
        return repo.fetchData();
    }
}
Enter fullscreen mode Exit fullscreen mode

5.2 Test Code

import org.junit.jupiter.api.Test;
import org.mockito.*;
import org.junit.jupiter.api.extension.ExtendWith;

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

@ExtendWith(MockitoExtension.class)
class SampleServiceTest {

    @Mock
    private SampleRepository repo;

    @InjectMocks
    private SampleService service;

    @Test
    void getData_returnsRepoData() {
        when(repo.fetchData()).thenReturn("Data from DB");

        String data = service.getData();

        assertEquals("Data from DB", data);
        verify(repo).fetchData();
    }

    @Test
    void getData_repoThrows_exceptionPropagates() {
        when(repo.fetchData()).thenThrow(new RuntimeException("DB down"));

        assertThrows(RuntimeException.class, service::getData);
    }
}
Enter fullscreen mode Exit fullscreen mode

6. Annotation Cheat‑Sheet

// JUnit 5 Annotations
@Test                // Marks a method as a test case
@BeforeEach          // Runs before every test for setup
@AfterEach           // Runs after every test for cleanup
@BeforeAll           // Runs once before all tests (static method)
@AfterAll            // Runs once after all tests (static method)
@DisplayName         // Gives a custom name to a test method

// Mockito Annotations
@ExtendWith(MockitoExtension.class)  // Enables Mockito in JUnit 5 tests
@Mock                                // Creates a mock instance
@InjectMocks                         // Injects mocks into the class under test
@Spy                                 // Partial mock: real methods run unless stubbed
@Captor                              // Captures arguments passed to mocks
@MockBean                            // Replaces a Spring bean in the test context

// Spring Test Annotations
@WebMvcTest(Controller.class)        // Slice test for controllers
@SpringBootTest                      // Full integration test with application context
@DataJpaTest                         // Slice test for JPA repositories
@AutoConfigureMockMvc                // Auto-configures MockMvc in full-context tests
Enter fullscreen mode Exit fullscreen mode

7. Putting It All Together: Mini‑Project Walkthrough

  1. Create a Spring Boot project with packages:
  • controllerSampleController
  • serviceSampleService
  • repositorySampleRepository
  1. Write Controller returning a simple string.

  2. Write Service that calls repository.

  3. Write Tests in src/test/java:

  • Controller Test with @WebMvcTest (no real server)
  • Service Test with Mockito
  1. Run Tests:
  • IDE (right‑click → Run)
  • CLI: ./mvnw test / mvn test

By the end, you’ll have isolated, fast, and clear tests for each layer of your application.


8. Best Practices & Tips

  • Arrange–Act–Assert: structure every test for readability.
  • Name tests descriptively: methodName_condition_expectedResult.
  • Test both happy & sad paths (normal and error cases).
  • Keep tests fast: mock external collaborators; avoid real I/O.
  • Avoid over‑mocking: only mock what you need.
  • Leverage slice tests: @WebMvcTest for controllers, @DataJpaTest for repositories.

With JUnit, Mockito, and MockMvc in your toolbox, you’re ready to build rock‑solid Spring Boot applications. Happy testing!

Top comments (0)