Spring Services Testing Blueprint
This blueprint applies the organization Testing Strategy to Spring Boot services.
Versions are intentionally not pinned here. Resolve current versions of Java, Spring Boot, JUnit, Mockito, Testcontainers-JVM, Pact-JVM, REST Assured, Spring Cloud OpenFeign, and JaCoCo from the Tech Radar (use the
technology-radar-blipskill). This keeps the blueprint stable as the radar evolves.
Goals
The test suite should:
- Run in the minimum possible time — reuse context, choose the right layer for each case, and avoid duplication between layers.
- Provide early feedback — apply the testing pyramid; always test at the lowest viable layer.
Testing Pyramid & Layer Optimization
/\
/ \ 5. System Tests — fewest, slowest, black-box
/----\
/ \ 4. Contract Tests — service boundary contracts
/--------\
/ \ 3. Component IT — full Spring Boot context, critical flows only
/------------\
/ \ 2. Sliced IT — sliced context per architectural layer
/----------------\
/ \ 1. Unit Tests — most, fastest, pure logic
Test at the Lowest Viable Layer
Every scenario must live at the lowest layer that can meaningfully verify it. Move up only when the lower layer cannot exercise the scenario without unreasonable complexity or loss of fidelity.
| Scenario | Layer |
|---|---|
| Pure business logic, algorithm, calculation | Unit |
| Controller request mapping, serialization, validation | Sliced IT (@WebMvcTest) |
| Repository queries, JPA mappings | Sliced IT (@DataJpaTest) |
| Cross-layer flow within a single service | Component IT |
| Service-to-service API/message contract | Contract |
| Critical end-to-end flow on a deployed environment | System |
Layer Boundary Rules
- If a bug can be caught with a plain JUnit + Mockito test, it MUST be caught there — do not write an integration test for something a unit test can cover.
- Sliced IT MUST NOT duplicate assertions already covered by unit tests.
- Component IT MUST be used only for scenarios that require the full application wiring, and the set MUST be kept small and focused on critical happy/unhappy paths.
- Contract tests verify compatibility, not business correctness; they MUST NOT re-validate logic already covered at lower layers.
- System tests MUST NOT duplicate contract or integration assertions.
Anti-Patterns
- Pyramid inversion: more integration/system tests than unit tests.
- Cross-layer duplication: the same business rule asserted at multiple layers. Pick the lowest; trust the others.
@SpringBootTestby default: always prefer a slice annotation (@WebMvcTest,@DataJpaTest, etc.).- Testcontainers everywhere: use only when vendor-specific infrastructure behaviour is under test.
- System tests as a safety net: fix coverage gaps at the right layer instead.
Layer Decision Checklist
Before generating any test class, answer in order:
- Only internal logic, no infrastructure? → Unit Test
- Requires a real HTTP mapping, JPA mapping, or Kafka listener but nothing beyond? → Sliced IT (appropriate slice)
- Requires the full application context wired together? → Component IT (justify why a slice is not enough)
- Verifies the agreed contract with another service? → Contract Test
- Verifies the deployed system from the outside? → System Test
Layer Implementation
1. Unit Tests
- Naming:
[ClassName]Test.java— methods:given[Condition]_when[Action]_then[Result]. - Dependencies: JUnit, Mockito, AssertJ (prefer AssertJ for all assertions).
- Plugin: Maven Surefire — include
**/*Test.java,**/*Tests.java. - Constraints: do not load the Spring context; use
@Spysparingly (legacy code or where full mocking is impractical).
2. Sliced IT
- Scope: repositories, REST controllers, Kafka listeners, JSON serialization.
- Naming:
[ClassName]IT.java(e.g.,UserRepositoryIT.java,UserControllerIT.java). - Dependencies: JUnit, Testcontainers.
- Plugin: Maven Failsafe — include
**/*IT.java; phaseintegration-test; goalsintegration-test,verify. - Constraints:
- Do not use
@SpringBootTestunless strictly necessary — prefer the matching slice annotation. - Use Testcontainers only when validating real infrastructure behaviour (vendor-specific queries, Kafka event flow, message serialization); otherwise prefer mocks or embedded alternatives.
- Prefer reusable static containers:
@Testcontainers+ astatic @Containerat class level.
- Do not use
3. Component IT
- Naming:
[ApplicationName]IT.javaor[FeatureName]IT.java. - Dependencies: JUnit, Testcontainers. Same Failsafe configuration as Sliced IT.
- Constraints:
- Use
@SpringBootTest(webEnvironment = RANDOM_PORT). - Implement DB state cleanup before/after tests (SQL scripts,
@Transactional, or repository calls).
- Use
4. Contract Tests
Scope: HTTP API contracts and asynchronous message/event contracts — compatibility only, not business correctness.
Option A: Pact (consumer-driven)
The consumer defines the contract first; the provider verifies it.
- Dependencies: Pact-JVM.
- Pact Broker: resolve the organization Pact Broker URL from platform configuration — do not hardcode it in source.
- Naming & execution:
- Consumer:
[ServiceName]ContractTest.java— Maven Surefire (testphase). - Provider:
[ProviderName]ContractTest.java— Maven Failsafe (integration-testphase).
- Consumer:
- Build integration:
- Consumer pipeline: run contract tests → generate pact files → publish to the Broker.
- Provider pipeline: fetch contracts from the Broker → run provider verification → publish results.
- Provider constraint: use
@SpringBootTest(webEnvironment = RANDOM_PORT)only if the provider must start inside the test.
Option B: Spring Cloud Contract (producer-driven)
The producer defines the contract, validates it against its own code, and shares the generated stubs.
- Dependencies:
spring-cloud-starter-contract-verifier(producer),spring-cloud-starter-contract-stub-runner(consumer). - Contract storage: shared Git repository or Nexus/Artifactory, per company configuration.
- Naming & execution:
- Producer: requires a base test class (e.g.,
BaseContractTest.java); generation and verification run via thespring-cloud-contract-maven-pluginduring theintegration-test/verifyphases. - Consumer:
[ConsumerName]ContractTest.java— uses@AutoConfigureStubRunnerto fetch and run stubs during the Surefire (test) phase.
- Producer: requires a base test class (e.g.,
- Build integration:
- Producer pipeline: define contracts → run the build to auto-generate and execute validation → publish the
-stubs.jar. - Consumer pipeline: configure Stub Runner to fetch the latest
stubs.jar→ run consumer tests against the local in-memory stub server.
- Producer pipeline: define contracts → run the build to auto-generate and execute validation → publish the
- Producer constraint: define the
BaseContractTestand map it in the plugin; use@SpringBootTestor lightweightRestAssuredMockMvc/WebTestClientconfiguration to mock controller contexts without booting external infrastructure.
5. System Tests
- Scope: They cover a deployed service or a set of deployed services that work together for a business flow.
- The app is NOT started by the test suite — it must be already deployed.
- Tests interact with services only via HTTP or events.
- Flow candidate criterion: A flow is a system-test candidate if validating its correctness requires simultaneously exercising more than one testing boundary — i.e., it cannot be fully asserted by unit, contract, or integration tests alone.
- Dependencies: JUnit + one HTTP client (REST Assured, WebTestClient standalone, or Spring Cloud OpenFeign).
- Naming:
[SystemName]SystemTest.javaor[FeatureName]SystemTest.java. - Plugin: Maven Failsafe — include
**/*SystemTest.java; phaseverify(post-deployment). - Constraints:
- Do not mock external calls — validate real integrations.
- Focus on critical smoke/regression flows, not exhaustive endpoint coverage.
- Read configuration from environment variables (e.g.,
SYSTEM_BASE_URL,AUTH_TOKEN,KAFKA_BOOTSTRAP_SERVERS); use a dedicatedsystemtestprofile for client config. - Provision/clean up test data via approved APIs or fixtures; avoid direct DB writes unless explicitly allowed.
- Assert system-level outcomes (state changes, emitted events, downstream effects), not only HTTP status codes.
Coverage (JaCoCo)
JaCoCo is integrated in the parent POM. Pin the JaCoCo version that is compatible with the project JDK — resolve the current compatible version from the Tech Radar rather than hardcoding it long-term.
Done When
- Unit tests run with Maven Surefire
- Component IT and Sliced IT run with Maven Failsafe
- JaCoCo version is compatible with the project JDK
- The Maven project builds successfully