Testing Spring Applications with JUnit 5 and Mockito

Interview Preparation Hub for Backend and Cloud-Native Engineering Roles

1. Introduction

Testing is the backbone of reliable software development. In Spring applications, testing ensures that business logic, APIs, and integrations work as expected. JUnit 5 provides a modern, flexible testing framework, while Mockito enables mocking dependencies for isolated unit tests. Together, they empower developers to write clean, maintainable, and effective tests.

This guide covers everything from basics to advanced techniques: unit testing, integration testing, mocking, parameterized tests, dynamic tests, CI/CD integration, best practices, common mistakes, and interview notes. By the end, you will have mastered testing Spring applications with JUnit 5 and Mockito.

2. JUnit 5 Basics

JUnit 5 introduces annotations and features that simplify testing:

  • @Test – Marks a test method.
  • @BeforeEach – Runs before each test.
  • @AfterEach – Runs after each test.
  • @DisplayName – Provides a readable name for tests.
  • @Nested – Groups related tests.
  • @ParameterizedTest – Runs tests with multiple inputs.
  • @RepeatedTest – Runs a test multiple times.
class CalculatorTest {

  private Calculator calculator;

  @BeforeEach
  void setUp() {
    calculator = new Calculator();
  }

  @Test
  @DisplayName("Addition should return correct sum")
  void testAddition() {
    assertEquals(5, calculator.add(2, 3));
  }

  @ParameterizedTest
  @ValueSource(ints = {1, 2, 3})
  void testPositiveNumbers(int number) {
    assertTrue(calculator.isPositive(number));
  }
}
    

Parameterized tests allow you to run the same test logic with different inputs, reducing duplication and improving coverage.

3. Mockito Basics

Mockito allows mocking dependencies to isolate units of code. This is crucial in Spring applications where services often depend on repositories or external APIs.

  • @Mock – Creates a mock object.
  • @InjectMocks – Injects mocks into the tested class.
  • when(...).thenReturn(...) – Defines mock behavior.
  • verify(...) – Verifies interactions with mocks.
  • ArgumentCaptor – Captures arguments passed to mocks.
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

  @Mock
  private UserRepository userRepository;

  @InjectMocks
  private UserService userService;

  @Test
  void testFindUser() {
    User user = new User(1L, "Naresh");
    when(userRepository.findById(1L)).thenReturn(Optional.of(user));

    User result = userService.getUserById(1L);

    assertEquals("Naresh", result.getName());
    verify(userRepository).findById(1L);
  }
}
    

4. Advanced Mockito Usage

Advanced features include spies, argument captors, and custom answers:

@Test
void testArgumentCaptor() {
  ArgumentCaptor captor = ArgumentCaptor.forClass(User.class);

  userService.createUser(new User(null, "Naresh"));
  verify(userRepository).save(captor.capture());

  assertEquals("Naresh", captor.getValue().getName());
}
    

Spies allow partial mocking, where real methods are called unless stubbed.

5. Integration Testing with Spring Boot

Spring Boot provides @SpringBootTest for integration testing. MockMvc allows testing controllers without starting a full server.

@SpringBootTest
@AutoConfigureMockMvc
class UserControllerIntegrationTest {

  @Autowired
  private MockMvc mockMvc;

  @Test
  void testGetUserEndpoint() throws Exception {
    mockMvc.perform(get("/api/users/1"))
           .andExpect(status().isOk())
           .andExpect(jsonPath("$.name").value("Naresh"));
  }
}
    

6. CI/CD Integration

Tests should run automatically in CI/CD pipelines. Tools like Jenkins, GitHub Actions, and GitLab CI can execute JUnit tests and report results.

Code coverage tools like JaCoCo ensure that critical paths are tested. Integrating with SonarQube provides insights into test quality and maintainability.

7. Best Practices

  • Write unit tests for business logic with Mockito.
  • Use integration tests for end-to-end scenarios.
  • Keep tests independent and repeatable.
  • Use descriptive names and @DisplayName for clarity.
  • Verify interactions with mocks to ensure correct behavior.
  • Test edge cases and negative scenarios.
  • Integrate tests into CI/CD pipelines.
  • Measure coverage but focus on meaningful tests, not just numbers.

8. Common Mistakes

  • Overusing mocks instead of writing integration tests.
  • Not verifying interactions, leading to false positives.
  • Mixing unit and integration tests in the same class.
  • Ignoring edge cases and negative scenarios.
  • Writing brittle tests that depend on implementation details.

9. Interview Notes

Testing is a common topic in technical interviews for backend and full‑stack roles. Interviewers want to see not only that you can write tests, but that you understand the philosophy behind them, the tools available, and the trade‑offs between different approaches.

  • Unit vs Integration Tests: Be ready to explain the difference. Unit tests isolate a single class or method, often using Mockito to mock dependencies. Integration tests verify that multiple layers (controller, service, repository) work together correctly, often using @SpringBootTest and MockMvc.
  • Mockito Usage: Discuss how mocking helps isolate business logic. Be able to show examples of @Mock, @InjectMocks, when(...).thenReturn(...), and verify(...).
  • JUnit 5 Features: Mention annotations like @ParameterizedTest, @Nested, and @DisplayName. These demonstrate familiarity with modern testing practices.
  • Best Practices: Emphasize writing independent, repeatable tests, using descriptive names, and testing edge cases.
  • CI/CD Integration: Explain how tests fit into pipelines, how coverage is measured, and how failures are reported.
  • Common Pitfalls: Be able to identify mistakes like over‑mocking, brittle tests, or ignoring negative scenarios.
Diagram: Interview Prep Map

Unit Testing → Mocking → Integration Testing → Advanced JUnit 5 → CI/CD → Best Practices → Pitfalls

10. Final Mastery Summary

JUnit 5 and Mockito together form the foundation of modern testing in Spring applications. JUnit 5 provides a flexible, annotation‑driven framework for writing tests, while Mockito enables mocking and verification of dependencies. Mastery of these tools means you can confidently test business logic, APIs, and integrations.

In this guide, we explored:

  • JUnit 5 basics and advanced features like parameterized and nested tests.
  • Mockito basics and advanced usage with argument captors and spies.
  • Integration testing with @SpringBootTest and MockMvc.
  • CI/CD integration for automated testing pipelines.
  • Best practices for writing clean, maintainable tests.
  • Common mistakes to avoid, such as over‑mocking or brittle tests.
  • Interview notes to prepare for technical discussions.

By applying these concepts, you ensure that your Spring applications are reliable, maintainable, and production‑ready. Testing is not just about catching bugs — it’s about building confidence in your codebase, enabling faster iteration, and supporting long‑term scalability.

Diagram: Mastery Roadmap

JUnit 5 Basics → Mockito Basics → Advanced Testing → Integration Testing → CI/CD → Best Practices → Mastery