Intermediate · Prove

Use MVC, JSON & JPA Test Slices

Load focused framework infrastructure and understand exactly what each slice excludes.

Primary source: Spring Boot 3.5 Reference: Testing

45 min

Lesson 3 of 4

1d streak

The Architectural Question

@SpringBootTest everywhere pays full-context cost even when the assertion only needs one mapper; slices load exactly enough machinery to fail meaningfully.

What this guide proves

  • Use `@WebMvcTest` for HTTP mapping and error contracts
  • Use `@DataJpaTest` for mappings and queries
  • Import only the additional collaborators the slice requires

Spring Boot Test Slices: Focused Context Testing

Spring Boot introduced Test Slices to bridge the gap between fast isolated unit tests and heavy @SpringBootTest integration tests. A test slice loads only the specific slice of the Spring ApplicationContext required to test a single architectural layer, mocking or omitting everything else.

Slice AnnotationBeans Loaded in ContextPrimary Purpose & Speed
@WebMvcTestControllers, ControllerAdvice, Filters, Jackson, SecurityTesting HTTP routing, serialization, validation, and status codes. Very fast (~1s).
@DataJpaTestRepositories, Entities, DataSource, EntityManager, FlywayTesting custom JPQL, derived queries, and transaction rollback. Fast (~2s).
@JsonTestJackson ObjectMapper, JsonComponent serializers/deserializersTesting custom JSON serialization formats and date formatting. Instant (~500ms).
@RestClientTestRestClient / RestTemplate builders, MockRestServiceServerTesting external REST client HTTP adapters without live network calls.

Active Recall Checkpoint

Why does @WebMvcTest(OrderController.class) NOT load @Service or @Repository beans into the ApplicationContext?

Hint: How does slice isolation prevent loading the entire application graph?

Show answer & explanation

By design, @WebMvcTest restricts scanning exclusively to MVC web components (@Controller, @ControllerAdvice). Collaborating business services must be supplied via @MockitoBean (or @MockBean), keeping the test focused and fast.

Testing Controllers with @WebMvcTest & ProblemDetail Validation

Using @WebMvcTest with MockMvc allows you to test HTTP semantics (status codes, headers, JSON body serialization, validation errors) without starting a real HTTP servlet container or listening on a network port. If the test asserts application/problem+json or custom fields such as invalidParams, import the application's @RestControllerAdvice or enable and configure MVC Problem Details explicitly; those fields are not guaranteed by the slice alone.

OrderControllerTest.java
package com.example.web;

import com.example.service.OrderService;
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.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

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

@WebMvcTest(OrderController.class)
class OrderControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockitoBean
    private OrderService orderService;

    @Test
    void createOrder_InvalidPayload_ReturnsProblemDetail() throws Exception {
        // Omitting required 'items' field triggers Jakarta Validation error:
        String invalidJson = """
            { "customerId": "" }
            """;

        mockMvc.perform(post("/api/v1/orders")
                .contentType(MediaType.APPLICATION_JSON)
                .content(invalidJson))
            .andExpect(status().isBadRequest())
            .andExpect(header().string("Content-Type", "application/problem+json"))
            .andExpect(jsonPath("$.title").value("Bad Request"))
            .andExpect(jsonPath("$.status").value(400))
            .andExpect(jsonPath("$.invalidParams").isArray());
    }
}

Spring Boot 3.4+ Note on @MockitoBean

Spring Framework 6.2 introduced @MockitoBean; in Boot 3.4+, the older Boot @MockBean is deprecated in favor of it.

Active Recall Checkpoint

What content type header does Spring Boot emit when returning an RFC 9457 ProblemDetail error response?

Hint: Look at the specialized media type for problem details.

Show answer & explanation

application/problem+json. Verifying this header in @WebMvcTest guarantees that API error contracts conform to modern RFC standards.

Testing Repositories with @DataJpaTest & TestEntityManager

@DataJpaTest configures an embedded test database when one is available, initializes Hibernate, and wraps each test method in a @Transactional boundary that rolls back automatically. A real PostgreSQL or Testcontainers database requires explicit configuration such as @AutoConfigureTestDatabase(replace = Replace.NONE) and, with Boot service connections, @ServiceConnection.

OrderRepositoryTest.java
package com.example.repository;

import com.example.entity.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
class OrderRepositoryTest {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private TestEntityManager entityManager;

    @Test
    void findPendingOrdersWithItems_FetchesWithoutNPlusOne() {
        Order order = new Order("cust_100");
        order.addItem("SKU-1", 2);
        entityManager.persist(order);

        // CRITICAL: Flush to generate INSERT SQL, then clear First-Level Cache:
        entityManager.flush();
        entityManager.clear();

        List<Order> results = orderRepository.findByStatusWithItems("PENDING");

        assertThat(results).hasSize(1);
        assertThat(results.get(0).getItems()).hasSize(1);
    }
}

Why flush() and clear() Are Mandatory in JPA Tests

If you do not call entityManager.clear(), Hibernate returns the cached entity instance directly from the First-Level Persistence Context without executing SQL SELECT queries! The test will pass even if your JPQL query has a syntax error or causes an N+1 query problem.

Active Recall Checkpoint

What does entityManager.clear() do, and why is it critical when testing custom repository queries?

Hint: What happens to the Hibernate Session / First-Level Cache?

Show answer & explanation

entityManager.clear() evicts all entities from the Hibernate First-Level Cache. This forces subsequent repository calls to execute real SQL SELECT queries against the database rather than reading cached in-memory objects.

Deliberate Failure Lab: The Flushed Persistence Illusion

Consider an entity with a database column constraint: @Column(nullable = false). A test persists the entity with a null field and immediately calls repository.findById(id). Without an explicit flush(), Hibernate delays writing SQL until transaction commit. Because the test method rolls back, SQL is never sent to the database, and the test passes green despite the constraint violation!

The Rule of JPA Testing

Always call entityManager.flush() immediately after creating entities in test setups to prove that table constraints, foreign keys, and column nullabilities are strictly satisfied by the database.

Active Recall Checkpoint

Why does Hibernate delay executing SQL INSERT statements until flush() or transaction commit?

Hint: What performance optimization is Hibernate performing?

Show answer & explanation

Hibernate uses write-behind caching (transactional write-behind) to batch SQL statements and delay execution until strictly necessary, which can mask database constraint errors in tests unless explicitly flushed.

Runtime Architecture & Flow

Decision canvas

Evaluate the choice across three constraints

These are comparison criteria, not sequential steps. A strong answer states which constraint dominates in the current system.

CRITERION 01

Use `@WebMvcTest` for HTTP mapping and error contracts

Ask how each option changes this guarantee, its operational cost, and its failure mode.

CRITERION 02

Use `@DataJpaTest` for mappings and queries

Ask how each option changes this guarantee, its operational cost, and its failure mode.

CRITERION 03

Import only the additional collaborators the slice requires

Ask how each option changes this guarantee, its operational cost, and its failure mode.

Decision prompt: Test a ProblemDetail response and an N+1-safe repository query.

Working Implementation

Code Walkthrough

Test a ProblemDetail response and an N+1-safe repository query.

OrderControllerTest.java
@WebMvcTest(OrderController.class)
class OrderControllerTest {
    @Autowired MockMvc mvc;
    @MockitoBean OrderQueries orders;

    @Test void rejects_an_empty_order() throws Exception {
        mvc.perform(post("/api/orders")
                .contentType(APPLICATION_JSON).content("{\"lines\":[]}"))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.title").value("Invalid request"));
    }
}
@WebMvcTest(OrderController.class) starts request mapping, Jackson, validation, and the advice chain, and leaves the persistence layer out.
@MockitoBean replaces OrderQueries, so a failure here points at the HTTP contract rather than at a repository or schema.

Synthesis & Production Verification

Production Acceptance Criteria

  • Assert a ProblemDetail error contract with @WebMvcTest
  • Prove an N+1-safe query with @DataJpaTest against Testcontainers
  • List what each slice excludes before importing anything

Architectural & Technical Review

Core Interview Questions

Q01What components are loaded by @WebMvcTest compared to @SpringBootTest?Show answer

@WebMvcTest loads only web infrastructure (@Controller, @ControllerAdvice, filters, Jackson, security), mocking services to focus strictly on HTTP routing, binding, and error status contracts.

Q02How does MockMvc verify RFC 7807/9457 ProblemDetail error contracts?Show answer

By inspecting status codes, verifying the application/problem+json content-type, and asserting on JSONPath properties ($.title, $.status, $.detail, $.invalidParams).

Q03Why must TestEntityManager.flush() and clear() be called in @DataJpaTest?Show answer

flush() forces Hibernate to execute SQL INSERT/UPDATE statements; clear() evicts entities from the First-Level Cache, ensuring queries hit the database rather than reading cached in-memory objects.

Production Pitfalls

Critical Anti-Patterns

  • Pitfall: Failing to flush and clear the TestEntityManager before repository assertions — Without clear(), Hibernate returns cached objects from memory, masking SQL syntax errors and database constraint bugs.
  • Pitfall: Using @SpringBootTest when @WebMvcTest or @DataJpaTest provides 10x faster feedback — Test slices boot in 1–2 seconds, whereas full context tests take 10–20 seconds per class.

Primary Documentation & Specifications

Supplementary Module Resources