Die Testpyramide bauen
Mach es vertrauenswürdig: schnelle Unit-Tests, gezielte Slice-Tests für Web und Daten und vollständige Integrationstests - die Pyramide, mit der du BookVault furchtlos änderst.
Deutsche Übersetzung in Arbeit
Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.
Auf dieser Seite
A service you can't change with confidence is a liability. Tests are what let you refactor, add features, and upgrade dependencies without fear. This lesson builds BookVault's test pyramid - many fast tests at the base, fewer slow ones on top - so every layer is covered at the right level.
The pyramid, bottom to top
╱╲ few full integration (@SpringBootTest, Testcontainers)
╱ ╲ - whole app, real DB; slowest, highest confidence
╱────╲ some slice tests (@WebMvcTest, @DataJpaTest)
╱ ╲ - one layer with real Spring wiring
╱────────╲ many unit tests (plain JUnit + Mockito)
╱__________╲ - pure logic, no Spring; millisecondsThe shape matters: fast unit tests catch most bugs cheaply and run constantly; a few slow integration tests confirm the whole thing works. Invert it - mostly slow end-to-end tests - and your suite becomes too slow to run and too brittle to trust.
Unit tests: pure logic, no Spring
The borrow rules are pure logic, so test them with mocked repositories - no container, no database, milliseconds per test:
@Test
void borrowReservesACopyAndRecordsALoan() {
when(books.findByIsbn("978-0134685991")).thenReturn(Optional.of(book)); // 2 copies
when(loans.save(any(Loan.class))).thenAnswer(inv -> inv.getArgument(0));
Loan loan = service.borrow("978-0134685991", 1L);
assertThat(book.getCopiesAvailable()).isEqualTo(1); // reserved one
verify(loans).save(any(Loan.class)); // recorded the loan
}Slice tests: one layer, really wired
Some things only work with Spring's machinery - request mapping, JSON, JPA queries. Slice tests load just that slice:
@WebMvcTestboots the web layer (controllers, JSON, validation) with the service mocked - so you test thatPOSTreturns201and bad input returns a400 ProblemDetail, without a database.@DataJpaTestboots JPA and repositories against an in-memory database - so you test thatfindByIsbnand yourJOIN FETCHqueries actually return what you expect.
Integration tests: the whole thing
At the top, @SpringBootTest loads the entire application. BookVault's security integration test
performs a real JWT login and checks the role rules end to end; a Testcontainers test runs the
full stack against a real PostgreSQL in a container, catching anything H2 would hide:
@SpringBootTest
@Testcontainers
class LendingPostgresIntegrationTest {
@Container
static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:16");
// ... exercises borrow/return against real Postgres
}Match the test to the risk
Test each concern at the cheapest level that's honest. Business rules? Unit tests. JSON mapping and status codes? A web slice. Query correctness? A data slice. 'Does the whole secured flow work against a real database?' Integration. Writing a slow @SpringBootTest for logic a unit test could cover just makes your suite slow for no extra confidence.
Tests that need Docker should skip gracefully
BookVault's Testcontainers test is annotated to skip when Docker isn't available, so ./mvnw test
stays green on any machine and the PostgreSQL test runs in CI (or locally with Docker) via
./mvnw verify. A test suite that fails merely because an optional dependency is absent trains people
to ignore red - keep the default run always-green.
For each, name the right test type and why: (a) 'borrowing the last copy leaves zero and a second borrow is rejected'; (b) 'POST /api/books with a blank title returns 400 with a ProblemDetail'; (c) 'a LIBRARIAN can log in and delete a book, a MEMBER cannot'. Order them from fastest to slowest.
What is the test pyramid, as BookVault applies it?
Key takeaways
- A test pyramid has many fast unit tests, some slice tests, and few full integration tests - inverting it makes the suite slow and brittle.
- Unit tests cover pure logic (borrow rules) with mocked repositories - no Spring, milliseconds each.
- @WebMvcTest boots the web slice (mapping, JSON, validation) with the service mocked; @DataJpaTest boots JPA and repositories against an in-memory DB.
- @SpringBootTest loads the whole app for end-to-end checks - a real JWT login and role rules, or the full stack on real PostgreSQL via Testcontainers.
- Test each concern at the cheapest honest level, and make Docker-dependent tests skip gracefully so the default run stays green.
- A good pyramid is what lets you change BookVault fearlessly.