Spring Boot / Intermediate: data, security, and framework evidence / Lesson 10
Testing slices and evidence boundaries
Choose domain, MVC, configuration, and persistence tests according to the behavior they can actually observe, using controlled identities and dependencies.
On this page
Lesson goals
- Distinguish direct calls, slices, and application contexts.
- Read MockMvc and MockitoBean fixtures in the pinned baseline.
- Avoid listener and transaction-cleanup assumptions.
Select the narrowest test that sees the risk
A domain unit test can observe calculation and explicit collaborators. A web slice can observe routing, conversion, advice, and selected filter integration. A configuration context can observe binding and bean conditions. A database integration test can observe actual mapping and transaction behavior. Each answers a different question; a large test suite can still miss the one boundary a claim depends on.
Use requirement-derived assertions instead of mirroring private field names or every implementation branch. Test invalid, absent, forbidden, and conflict states as well as success. Keep fixtures synthetic and bound in size. A test name should reveal the behavior and the evidence boundary, such as โmissing product returns404 through MVC,โ rather than merely โtest service.โ
Exercise an intended web contract without a real port
The illustrative @WebMvcTest loads the selected controller slice and explicitly imports the intended security chain. @MockitoBean, available in the matching Framework6.2 test support, provides the contextual lookup capability. A synthetic authority permits the GET under the shown policy. Appropriate Boot test and security-test dependencies are required; the fixture is not run here and no listener is started.
MockMvc dispatches through a mock servlet environment rather than proving a production network path. It can test selected filters and MVC serialization, while a mock principal does not verify real credentials. Keep expected fields exact and include missing/forbidden cases. Do not disable all filters merely to get a200 assertion unless the test explicitly targets a different lower-level boundary.
package training.catalog;
import java.util.Optional;
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.context.annotation.Import;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(ProductController.class)
@Import(ApiSecurity.class)
class ProductControllerTest {
@Autowired MockMvc mvc;
@MockitoBean ProductService products;
@Test
@WithMockUser(authorities="catalog:read")
void knownProduct() throws Exception {
given(products.find(42L)).willReturn(Optional.of(new ProductView(42L,"Notebook")));
mvc.perform(get("/products/42"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(42))
.andExpect(jsonPath("$.name").value("Notebook"));
}
}
This test requires the contextual controller/service contracts and managed test dependencies. Its assertions are intended, not an observed framework test run.
Keep configuration tests limited to their intended beans
ApplicationContextRunner can configure a small context and assert bean presence, property binding, or expected startup failure. The second fixture imports only the properties configuration and supplies bounded values. A real run would need the validation/binding dependencies. It is not an instruction to load the entire application's production properties or connect to configured integrations. Syntax review alone is narrower than executing this context.
Also test an absent required value, an invalid page bound, profile conditions, and conflicting overrides. A broad @SpringBootTest can accidentally initialize schedulers, clients, or data access that the question did not require. Choose and restrict the context deliberately. A test that passes because it never registered the relevant configuration is not evidence that the configuration is correct.
package training.catalog;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
class CatalogConfigurationTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withUserConfiguration(CatalogConfiguration.class);
@Test
void bindsKnownProperties() {
runner.withPropertyValues("catalog.max-page-size=20","catalog.timeout=2s")
.run(context -> {
assertThat(context).hasNotFailed();
assertThat(context).hasSingleBean(CatalogProperties.class);
assertThat(context.getBean(CatalogProperties.class).maxPageSize()).isEqualTo(20);
assertThat(context.getBean(CatalogProperties.class).timeout()).isEqualTo(Duration.ofSeconds(2));
});
}
}
The fixture states intended assertions for the selected configuration. No context refresh or validator execution is claimed.
Know when a test starts infrastructure
@SpringBootTest defaults to a mock web mode rather than a real listening server; RANDOM_PORT and DEFINED_PORT start embedded listeners. NONE uses a non-web context but can still initialize user beans and external integrations. Select the mode and dependencies explicitly. This course starts none of those contexts or listeners merely to claim a framework execution result.
A transaction around a test method does not automatically roll back work performed by an HTTP server on another thread. Likewise, a test using another database dialect may not reproduce production locks or migrations. Use explicit cleanup and isolated approved storage when such integration is authorized. Never rely on โtestโ in an annotation to protect a configured project datasource.
Make failures and evidence reproducible
Control clocks, identifiers, fakes, and asynchronous scheduling rather than sleeping until an assertion usually passes. Observe failed futures and verify cleanup. Assert no forbidden mutation after rejection, not only an error status. Keep contract fixtures and consumer checks for public DTO evolution, while avoiding brittle snapshots of internal exception lines or unstable diagnostic text.
Record the test scope, dependencies, runtime, command, and actual outcome. Plain Java models and JSON/YAML/XML parsing in this course are labeled separately from unexecuted Spring tests. A helpful suite tells the next maintainer which behavior regressed and which boundary was observed. Passing code discovery or compiling a DTO is not equivalent to passing MVC, security, and persistence integration.
Common errors and fixes
A test unexpectedly opened a listening port
- Why it happens
- A real web-environment mode was selected.
- How to fix it
- Use an appropriate mock/non-web boundary or explicitly authorized isolated integration mode.
- Prevent it next time
- Review test startup scope.
A test rollback left server-created data behind
- Why it happens
- Server work ran on another thread/transaction.
- How to fix it
- Use isolated storage and explicit cleanup for that test architecture.
- Prevent it next time
- Do not infer rollback from the test method alone.
A web test passed only after all filters were disabled
- Why it happens
- The test stopped observing the security boundary it claimed to cover.
- How to fix it
- Retain intended filters and supply a controlled valid identity or CSRF case.
- Prevent it next time
- Name the evidence boundary explicitly.
Check your understanding
1. Which test best observes JSON binding and the selected400 problem mapping without a real port?
Show worked solution
A properly configured MVC slice or mock-web fixture including the controller, relevant advice, validator, and selected filters. A direct method call skips those integrations, while a full random-port environment adds an unrelated listener and different transaction considerations.
2. Why should a MockitoBean result not be cited as evidence of generated SQL correctness?
Show worked solution
The mock substitutes application behavior and executes no JPA query. It can prove delegation and transport mapping for the supplied result, while mappings, constraints, query plans, and transaction semantics need their own controlled database tests.
Key takeaways
- Match the test scope to the behavior being claimed.
- Mock web, configuration contexts, and real listeners are different modes.
- Truthful evidence includes failure paths, cleanup, and explicit limitations.