Slice Testing Interview Questions & Answers
Spring Boot test slices — @WebMvcTest with MockMvc, @DataJpaTest with TestEntityManager and an embedded DB, @JsonTest, @RestClientTest, @MockBean for slice collaborators, and how slices keep the context small and fast.
A test slice loads only the part of the context relevant to one layer instead of the whole
application. @WebMvcTest loads the web layer, @DataJpaTest the persistence layer, @JsonTest the
serialization layer — each registers only the beans that layer needs.
@WebMvcTest(OrderController.class) // controllers + MVC infra only
@DataJpaTest // repositories + JPA + embedded DB
@JsonTest // Jackson + JSON beans
Because a slice skips your services, security config, schedulers, etc., it starts far faster than
@SpringBootTest while still testing real framework behavior (routing, JSON binding, SQL). It's the
middle tier of the test pyramid: more realistic than a unit test, lighter than a full integration test.
Rule of thumb: Use a slice when you want real framework wiring for one layer without paying to boot the entire application.
@WebMvcTest boots only the web layer — controllers, @ControllerAdvice, filters, converters,
and an auto-configured MockMvc — but not @Service/@Repository/@Component beans. You
provide collaborators as mocks.
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired MockMvc mvc;
@MockBean OrderService service; // controller's dependency, mocked
@Test void returnsOrder() throws Exception {
when(service.find(1L)).thenReturn(new Order(1L, "PAID"));
mvc.perform(get("/orders/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("PAID"));
}
}
It tests routing, request mapping, validation, serialization, and exception handling without a
server or a database. Naming the controller (@WebMvcTest(OrderController.class)) keeps the slice to
a single controller.
Rule of thumb: @WebMvcTest + MockMvc is the standard way to test controller behavior; mock the
services it calls with @MockBean.
MockMvc drives the Spring MVC DispatcherServlet in-process — no Tomcat, no socket, no
real HTTP. It builds a mock request, runs it through the full MVC machinery (mapping, argument
resolution, the controller, exception handling, view/JSON rendering), and lets you assert on the result.
mvc.perform(post("/orders")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"item\":\"widget\"}"))
.andExpect(status().isCreated())
.andExpect(header().exists("Location"))
.andExpect(jsonPath("$.id").isNumber());
Because it skips the network it's fast and deterministic, but it does not exercise the servlet
container, real connection handling, or HTTP-level concerns. For those you need a running server
(@SpringBootTest(webEnvironment = RANDOM_PORT) + TestRestClient/WebTestClient).
Rule of thumb: Use MockMvc to test controller logic fast; use a real port only when you must
verify true HTTP/container behavior.
By design. A slice uses a filtered component scan that includes only beans relevant to its layer —
@Controller, @ControllerAdvice, Converter, Filter, Jackson modules — and excludes
@Service, @Repository, and ordinary @Components. That's what keeps the context tiny.
@WebMvcTest(OrderController.class)
class T {
@MockBean OrderService service; // REQUIRED — not scanned, you supply it
}
If you forget to provide a collaborator the controller needs, the context fails with
"No qualifying bean of type ... OrderService". The slice expects you to mock the layers below
with @MockBean.
Rule of thumb: In a @WebMvcTest, every service the controller depends on must be a @MockBean —
the slice deliberately leaves them out.
@DataJpaTest boots the persistence slice: JPA @Entity classes, Spring Data repositories,
EntityManager, a TestEntityManager, and — by default — an in-memory embedded database. It does
not load services or controllers.
@DataJpaTest
class OrderRepositoryTest {
@Autowired OrderRepository repo;
@Autowired TestEntityManager em;
@Test void findsByStatus() {
em.persist(new Order("PAID"));
assertThat(repo.findByStatus("PAID")).hasSize(1);
}
}
It's also @Transactional by default, so each test rolls back at the end — the database is clean
between tests. Ideal for verifying custom queries, derived methods, mappings, and constraints against a
real SQL engine.
Rule of thumb: Use @DataJpaTest to test repositories and JPA mappings against a real (embedded)
database, with automatic rollback keeping tests isolated.
TestEntityManager is a test-scoped wrapper around the JPA EntityManager for arranging and inspecting data without going through the repository you're testing. Using the repo to set up its own test can hide bugs (a broken save would mask a broken find).
Long id = em.persistAndGetId(new Order("PAID"), Long.class); em.flush(); // force SQL now em.clear(); // detach so the next read hits the DB, not the cache
Order found = repo.findById(id).orElseThrow(); // exercises the repo ```
`persistAndFlush`, `flush`, and `clear` let you control exactly what's in the DB vs. the persistence context — crucial for tests that must read fresh from the database rather than the first-level cache.
**Rule of thumb:** Set up and verify with `TestEntityManager`; exercise the **repository** with the call you're actually testing.
By default @DataJpaTest replaces any configured datasource with an embedded one (H2/HSQLDB).
Disable that replacement to test against the real engine — important because H2 and Postgres differ in
SQL dialect and constraints.
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
class OrderRepositoryIT {
@Container @ServiceConnection
static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:16");
// repo tests run against real Postgres
}
Testing on H2 risks "passes locally, fails in prod" when you use Postgres-specific types, JSON columns,
or native queries. Replace.NONE plus Testcontainers gives a real database while keeping the
slim slice.
Rule of thumb: H2 is convenient but not your prod DB — for query-heavy or dialect-specific
repositories, use @AutoConfigureTestDatabase(replace = NONE) with Testcontainers.
@JsonTest boots just the JSON serialization slice — Jackson ObjectMapper, configured modules,
and test helpers — to verify your DTOs serialize and deserialize exactly as expected, including
field names, date formats, and @JsonIgnore.
@JsonTest
class OrderJsonTest {
@Autowired JacksonTester<Order> json;
@Test void serializes() throws Exception {
var result = json.write(new Order(1L, "PAID"));
assertThat(result).hasJsonPathValue("$.status");
assertThat(result).extractingJsonPathStringValue("$.status").isEqualTo("PAID");
}
@Test void deserializes() throws Exception {
var order = json.parseObject("{\"id\":1,\"status\":\"PAID\"}");
assertThat(order.getStatus()).isEqualTo("PAID");
}
}
It uses your real, app-configured ObjectMapper, so it catches mapping issues a hand-rolled mapper
in a unit test would miss (custom serializers, naming strategy, modules).
Rule of thumb: Use @JsonTest + JacksonTester to lock down the exact JSON shape of API
DTOs with the application's real Jackson configuration.
@RestClientTest slices in just the beans needed to test a client that calls an external API —
your RestClient/RestTemplate/WebClient builder plus a MockRestServiceServer that stubs the
remote endpoint's responses.
@RestClientTest(WeatherClient.class)
class WeatherClientTest {
@Autowired WeatherClient client;
@Autowired MockRestServiceServer server;
@Test void parsesResponse() {
server.expect(requestTo("/weather?city=NYC"))
.andRespond(withSuccess("{\"temp\":21}", MediaType.APPLICATION_JSON));
assertThat(client.tempFor("NYC")).isEqualTo(21);
}
}
It tests your outbound call — URL building, headers, response parsing, error handling — without hitting the real third party. The mock server also lets you simulate timeouts and 5xx to test your client's resilience.
Rule of thumb: Use @RestClientTest with MockRestServiceServer to test API clients deterministically,
without depending on the live remote service.
@MockBean adds (or replaces) a bean in the slice's context with a Mockito mock. In a @WebMvcTest
you use it to supply the services the controller calls, since the slice doesn't scan them; you then
stub the mock as usual.
@WebMvcTest(OrderController.class)
class T {
@Autowired MockMvc mvc;
@MockBean OrderService service;
@Test void ok() throws Exception {
when(service.find(1L)).thenReturn(new Order(1L, "PAID"));
mvc.perform(get("/orders/1")).andExpect(status().isOk());
}
}
Each distinct set of @MockBeans produces a different context configuration, which Spring caches
separately — too many unique combinations defeats context caching and slows the suite. (Boot 3.4+
introduces @MockitoBean as the replacement for the now-deprecated @MockBean.)
Rule of thumb: @MockBean is how slices get their collaborators — keep the set of mocked beans
consistent across tests so the cached context is reused.
Use a slice when you're testing one layer in isolation and want speed; use @SpringBootTest
when you need multiple layers wired together (controller → service → repository → DB) end to end.
@WebMvcTest → controller behavior, services mocked (fast)
@DataJpaTest → repository/query behavior, embedded DB (fast)
@SpringBootTest → real flow across all layers (slow)
Slices keep the context tiny and the feedback loop tight, so they should be the majority of your
Spring tests; full @SpringBootTest runs are valuable but expensive, so keep them few. This is the
test pyramid applied to Spring.
Rule of thumb: Default to the narrowest slice that covers the behavior; escalate to
@SpringBootTest only when a test genuinely spans layers.
@WebMvcTest does apply your Spring Security filter chain, so an unauthenticated request can get a
401/403 and fail your test. Use Spring Security's test support to supply a user and CSRF tokens.
@WebMvcTest(OrderController.class)
class T {
@Autowired MockMvc mvc;
@MockBean OrderService service;
@Test
@WithMockUser(roles = "ADMIN")
void adminCanDelete() throws Exception {
mvc.perform(delete("/orders/1").with(csrf())) // csrf() post-processor
.andExpect(status().isNoContent());
}
}
@WithMockUser injects an authenticated principal; .with(csrf()) supplies a valid CSRF token for
state-changing methods. Requires spring-security-test on the classpath. (You can also use
.with(user("u").roles("ADMIN")) per request.)
Rule of thumb: In @WebMvcTest, authenticate with @WithMockUser and add .with(csrf()) to
POST/PUT/DELETE, or the security chain will reject the request.
Use jsonPath(...) expectations, which evaluate JSONPath expressions against the response body —
far cleaner than string-matching the raw JSON.
mvc.perform(get("/orders/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.status").value("PAID"))
.andExpect(jsonPath("$.items").isArray())
.andExpect(jsonPath("$.items.length()").value(3))
.andExpect(jsonPath("$.secret").doesNotExist());
$ is the root; $.field reads a property; $.arr[0] indexes; .doesNotExist() asserts a field is
absent (e.g. a masked password). You can also pull the body out with andReturn() and parse it with
JacksonTester for richer assertions.
Rule of thumb: Assert response bodies with jsonPath, and use .doesNotExist() to prove sensitive
fields are not leaked.
The recurring traps:
- Using
@SpringBootTestwhen a slice would do — slow tests for no extra coverage. - Forgetting a
@MockBeanfor a controller dependency → "No qualifying bean" failure. - Assuming
@WebMvcTestignores security — it doesn't; add@WithMockUser/csrf(). - Trusting H2 in
@DataJpaTestfor Postgres-specific SQL → green locally, red in prod. - Setting up
@DataJpaTestdata through the repo under test instead ofTestEntityManager. - Spawning many unique
@MockBeancombinations, busting the context cache.
// Forgetting the mock — context won't start:
@WebMvcTest(OrderController.class)
class Broken { @Autowired MockMvc mvc; } // missing @MockBean OrderService
Rule of thumb: Pick the narrowest slice, mock its collaborators, remember security applies, and don't trust H2 for dialect-specific queries.
Slices deliberately omit unrelated auto-config, but you can opt specific pieces back in with the
@AutoConfigure... annotations, or import a specific config with @Import.
@WebMvcTest(OrderController.class)
@Import(JacksonConfig.class) // bring in a custom @Configuration
@AutoConfigureMockMvc(addFilters = false) // skip security filters if desired
class T { /* ... */ }
For example @AutoConfigureMockMvc(addFilters = false) disables the security chain in a web slice; an
@Import pulls in a converter or @ControllerAdvice that isn't component-scanned by the slice. This
lets you widen a slice just enough without escalating to a full @SpringBootTest.
Rule of thumb: Widen a slice surgically with @Import / @AutoConfigure... rather than jumping to
@SpringBootTest the moment something's missing.
More Testing interview questions
More ways to practice
The self-quiz is live. Join our channel for updates, new content & tech tips.