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
Lesson 3 of 4
1d streakThe 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 Annotation | Beans Loaded in Context | Primary Purpose & Speed |
|---|---|---|
| @WebMvcTest | Controllers, ControllerAdvice, Filters, Jackson, Security | Testing HTTP routing, serialization, validation, and status codes. Very fast (~1s). |
| @DataJpaTest | Repositories, Entities, DataSource, EntityManager, Flyway | Testing custom JPQL, derived queries, and transaction rollback. Fast (~2s). |
| @JsonTest | Jackson ObjectMapper, JsonComponent serializers/deserializers | Testing custom JSON serialization formats and date formatting. Instant (~500ms). |
| @RestClientTest | RestClient / RestTemplate builders, MockRestServiceServer | Testing 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.
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
@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.
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
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
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.
Use `@WebMvcTest` for HTTP mapping and error contracts
Ask how each option changes this guarantee, its operational cost, and its failure mode.
Use `@DataJpaTest` for mappings and queries
Ask how each option changes this guarantee, its operational cost, and its failure mode.
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.
@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"));
}
}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
Key Takeaway
Spring Boot Test Slices (@WebMvcTest, @DataJpaTest) isolate individual architectural layers, providing fast, targeted verification of HTTP contracts and SQL persistence without full context overhead.
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
Lesson Primary References
Supplementary Module Resources