PATTERN Cited by 1 source
Failsafe integration test separation¶
Pattern¶
Split Maven's test run into two phases with two different plugins:
- Surefire owns the
testphase and runs unit tests by naming convention (*Test/*Tests/*TestCase). - Failsafe owns the
integration-test+verifyphases and runs integration tests (*IntegrationTest).
Gate Failsafe behind a Maven profile (with-integration-tests
is the Zalando ZMS name) so:
mvn test— fast, Surefire only, seconds. Developer inner loop, laptop-friendly.mvn verify -P with-integration-tests— slower, Failsafe runs the IT suite. CI, demand-driven local runs, pre-commit gates.
Why separate phases¶
- Phase semantics matter. Unit-test failures should fail
fast in
test. IT failures are expensive (real containers, DB state) and shouldn't block the faster feedback loop by default. - Different plugin behaviour. Failsafe is explicitly
designed to always run
verifyeven after IT failures, giving post-test cleanup hooks a chance to run (important when shared containers are in play — see patterns/shared-static-container-across-tests). Surefire just aborts. - Naming convention enforces the discipline. Anyone
adding an IT that accidentally lands in Surefire (e.g. named
FooTestinstead ofFooIntegrationTest) slows the dev inner loop for everyone. Convention + plugin include-filters is the cheapest enforcement.
Mechanism¶
Minimum ZMS-style pom.xml fragment
(sources/2021-02-24-zalando-integration-tests-with-testcontainers):
<profiles>
<profile>
<id>with-integration-tests</id>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<includes>
<include>**/*IntegrationTest.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
</profiles>
CI invokes: mvn clean verify -P with-integration-tests.
Plain mvn test skips Failsafe entirely.
Variants¶
- Always-on Failsafe, skip-via-property instead of a
profile:
<skipITs>/-DskipITs=true. Equivalent semantics; profile is more discoverable. - Tag-based inclusion via JUnit 5
@Tag("integration")+ Surefire<groups>/<excludedGroups>. Reduces the need for two plugins but loses the phase-separation semantics. - Separate Gradle source sets (
src/integrationTest/java) for Gradle equivalents. Same pattern, different build tool.
Tradeoffs¶
- Second plugin to maintain. Version bumps and configuration for both.
- Naming-convention drift. Test classes that look unit-y but hit real resources slip through if the convention isn't policed. Code review + lint rules mitigate.
- CI config multiplies. CI pipelines must invoke the right
Maven targets; regression in CI config (dropping
-P with-integration-tests) silently disables the IT suite.
Seen in¶
- sources/2021-02-24-zalando-integration-tests-with-testcontainers —
Zalando Marketing Services's default pom layout: Surefire for
*Test, Failsafe for*IntegrationTest, profile gate to keep local inner loop fast.
Related¶
- systems/maven-surefire-plugin · systems/maven-failsafe-plugin
- concepts/test-pyramid — the layered discipline this plumbing implements.
- patterns/real-docker-container-over-in-memory-fake — the reason the IT suite is expensive enough to want behind a profile gate.