Start Learning
Javaneer
Back to stage
Module 5·Testing Spring Applications

Test Strategy, Data & CI

The test pyramid, choosing the right test for each job, managing test data, and running it all in CI.

14 min readIntermediate
On this page

You now have every kind of test in your toolbox: unit, web slice, data slice, integration, security. The final skill is strategy - how many of each, how to manage their data, and how to run them automatically so nothing regresses.

The test pyramid

A healthy suite is shaped like a pyramid: a wide base of fast, focused tests, and a narrow tip of slow, broad ones.

  • Base — unit tests (many). Milliseconds each. Pure logic. Run constantly.
  • Middle — slice tests (fewer). @WebMvcTest, @DataJpaTest. Test one layer for real.
  • Tip — integration/end-to-end (few). @SpringBootTest + Testcontainers. Prove the pieces work together.

Invert the pyramid - mostly slow end-to-end tests - and your suite becomes so slow that developers skip it, which defeats the purpose.

Smoke detectors everywhere, fire drills occasionally

A building has a smoke detector in every room - cheap, fast, everywhere - and runs a full fire drill only occasionally, because a drill is expensive and disruptive. Tests work the same way: cover the code densely with fast unit tests (smoke detectors) and run a few full-stack integration tests (fire drills) to confirm everything evacuates correctly together. Many cheap checks, a few expensive ones.

Managing test data

Flaky tests usually come from shared or leftover data. Keep tests independent:

  • Slice and @SpringBootTest tests roll back their transactions by default - lean on that.
  • Build the exact data a test needs inside the test, don't rely on global seed data.
  • With Testcontainers, each class gets a fresh database; don't assume one test's writes exist for another.

A good test is independent and repeatable

Any test should pass on its own, in any order, run a thousand times. If a test only passes after another one ran, or fails on the second run, it's coupled to shared state - fix the data setup, not the assertion.

Run it all in CI

Tests only protect you if they run automatically. A continuous integration pipeline runs the whole suite on every push and pull request, blocking a merge if anything fails:

# a CI step, conceptually
- run: ./mvnw verify        # compile + unit + slice + integration tests

mvn test runs unit and slice tests; mvn verify also runs integration tests (Testcontainers spins up its databases using the CI runner's Docker). Green build, safe to merge; red build, fix before merging. This is the safety net that lets you change BookVault confidently.

Shape the suite

BookVault has: business rules in BookService, controller behavior, repository queries, and end-to-end secured flows. Sketch roughly how many of each test type you'd write and why - and explain what goes wrong if you tested almost everything with @SpringBootTest.

What does the test pyramid recommend?

Key takeaways

  • Shape the suite like a pyramid: many fast unit tests, fewer slice tests, a few slow integration/end-to-end tests.
  • An inverted pyramid (mostly @SpringBootTest) is so slow that people stop running it - defeating its purpose.
  • Keep tests independent and repeatable: build each test's own data and rely on automatic rollback.
  • Run the whole suite in CI on every push/PR; mvn test covers unit/slice, mvn verify adds integration.
  • A fast, trustworthy, automated suite is what lets you change the app with confidence.
Was this lesson helpful?
Edit this page on GitHub