← Home
🏗Crack System Design with DSA AnimatorSystem DesignNEW
🏗 System Design›Architecture Styles

🧪JUnit Testing in Spring Boot (User Management System)

Learn testing systematically: unit -> web -> repository -> edge cases -> interview readiness.

📌 Key IdeaUse layered testing. Service tests with @Mock, controller tests with @WebMvcTest + @MockBean + MockMvc, repository tests with @DataJpaTest, and focused exception/edge-case tests. This gives fast feedback plus production confidence.
📖

Definition

JUnit is the core Java testing framework; Mockito provides test doubles; Spring Boot test slices let you test each layer in isolation without loading unnecessary context.

✨

Why It Matters

  • Prevents regressions during refactoring and feature additions.
  • Builds confidence for CI/CD pipelines and safer deployments.
  • Interviewers evaluate your ability to test design, not only write business code.
💻

Code Variations

Step 2: pom.xml test dependencyxml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>
In Spring Boot 2.4+ and 3.x this already includes JUnit 5 + Mockito.
Step 3: Service unit test (@Mock + @InjectMocks)java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
  @Mock UserRepository userRepository;
  @InjectMocks UserService userService;

  @Test
  void createUser_savesAndReturnsUser() {
    User user = new User(1L, "John Doe", "john@example.com");
    when(userRepository.save(any(User.class))).thenReturn(user);

    User saved = userService.createUser(user);

    assertEquals("John Doe", saved.getName());
    verify(userRepository).save(user);
  }
}
Step 4: Controller test (@WebMvcTest + MockMvc)java
@WebMvcTest(UserController.class)
class UserControllerTest {
  @Autowired MockMvc mockMvc;
  @MockBean UserService userService;

  @Test
  void getUserByEmail_returns200() throws Exception {
    User user = new User(1L, "Alice", "alice@example.com");
    when(userService.getUserByEmail("alice@example.com"))
        .thenReturn(Optional.of(user));

    mockMvc.perform(get("/users/alice@example.com"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.email").value("alice@example.com"));
  }
}
Step 5: Repository integration test (@DataJpaTest)java
@DataJpaTest
class UserRepositoryTest {
  @Autowired TestEntityManager em;
  @Autowired UserRepository userRepository;

  @Test
  void findByEmail_userExists_returnsUser() {
    em.persist(new User(null, "John", "john@example.com"));
    em.flush();

    Optional<User> found = userRepository.findByEmail("john@example.com");
    assertTrue(found.isPresent());
  }
}
flush() forces SQL execution before query assertions.
Step 6: Exception path test (duplicate email)java
@Test
void createUser_duplicateEmail_throwsConflict() {
  User req = new User(null, "John", "john@example.com");
  when(userRepository.findByEmail("john@example.com"))
      .thenReturn(Optional.of(new User(1L, "John", "john@example.com")));

  assertThrows(DuplicateEmailException.class, () -> userService.createUser(req));
}
📊

Which test type for which layer?

AspectUnit TestWeb Slice TestRepository Integration Test
Annotation@ExtendWith(MockitoExtension.class)@WebMvcTest@DataJpaTest
ScopeSingle class logicController + MVC configJPA repositories + DB
DependenciesMock all collaborators@MockBean service/depsReal JPA + test DB
SpeedFastestFastModerate
Best forBusiness rulesHTTP contractQuery correctness
🔀

Common Confusions Cleared Up

@Mock vs @MockBean

@Mock is pure Mockito (no Spring context). @MockBean registers/replaces a bean inside Spring test context (e.g., in @WebMvcTest, @SpringBootTest).

@WebMvcTest vs @SpringBootTest

@WebMvcTest is focused and fast for controller contracts. @SpringBootTest loads full app context and is used when you need broader integration.

persist() vs merge() in JPA tests

persist() is for new entities; merge() reattaches detached entities and returns managed instance.

🧪

Variations / Implementations

Step 1: Introduction (JUnit + Mockito)

JUnit structures tests (@Test, assertions), while Mockito isolates dependencies by stubbing behavior (when(...).thenReturn(...)). In layered Spring apps this is essential for true unit tests.

Step 2: Setup in Spring Boot

Use spring-boot-starter-test. It already includes JUnit 5, Mockito, AssertJ, JSONassert, and Spring test utilities. Avoid mixing JUnit 4 unless required by legacy code.

Step 3: Service unit tests

Test business logic with @ExtendWith(MockitoExtension.class), @Mock, and @InjectMocks. No real DB/network should be involved.

Step 4: Controller tests

Use @WebMvcTest + MockMvc + @MockBean to validate request mapping, status codes, payloads, and error responses without starting full server.

Step 5: Repository integration tests

Use @DataJpaTest for JPA repositories. Persist test data with TestEntityManager, call repository methods, and assert DB behavior.

Step 6: Exceptions and edge cases

Validate duplicate email, invalid input, not-found, DB error paths, and API contract (400/404/409/500) so failure handling is intentional.

Step 7: Best practices + interview prep

Keep tests deterministic, independent, and readable. Prefer behavior-focused names and verify interactions only when meaningful.

⚠️

Common Mistakes / Anti-Patterns

  • Using @SpringBootTest for every test (slow suite).
  • Mocking too much in repository tests (defeats integration purpose).
  • Asserting implementation details instead of observable behavior.
  • Tests dependent on execution order/shared mutable state.
  • No negative-path tests (only happy path).
🎤

Interview Q&A

🔮

Likely Follow-up Questions

  • How would you test async methods and scheduled jobs?
  • How to test security (@WithMockUser, JWT filters)?
  • When would you use Testcontainers over H2?
  • How to keep test data setup maintainable at scale?
🧾

Quick Cheat Sheet

Service test@Mock + @InjectMocks + behavior assertions.
Controller test@WebMvcTest + MockMvc + @MockBean.
Repository test@DataJpaTest + persist/flush + query assertions.
Failure testingAssert exceptions + HTTP contract for edge cases.
🔗

Related Topics