Testing Spring Boot Applications
Spring Boot ships an excellent testing toolkit (spring-boot-starter-test), bundling JUnit 5, AssertJ, Mockito, Spring's test framework, and JSON and HTTP testing utilities. The challenge isn't tooling. It's choosing the right level for each test: plain unit tests for business logic, test slices that load just one layer (web, JPA, JSON), and full @SpringBootTest integration tests against real infrastructure via Testcontainers.
Loading the entire application context for every test makes suites slow, while mocking everything hides integration bugs. A balanced pyramid, with fast unit tests at the base, focused slices in the middle, and a smaller set of realistic integration tests on top, gives confidence without 20-minute builds.
TL;DR
- Unit-test business logic without Spring: constructor injection lets you pass fakes directly.
- Test slices load only part of the context:
@WebMvcTest(controllers),@DataJpaTest(repositories),@JsonTest,@RestClientTest, and more. @SpringBootTestloads the full application for end-to-end integration tests, optionally with a real web server.- Use Testcontainers (with
@ServiceConnection) to test against real PostgreSQL, Kafka, Redis, and others instead of in-memory substitutes. - Replace beans with
@MockitoBean(Spring Boot 3.4+; formerly@MockBean) sparingly, since each variation creates a new context. - Keep contexts cacheable (consistent configuration) so Spring reuses them across tests.
Quick Example
A controller slice test and a full integration test with Testcontainers:
@ServiceConnection wires the container's JDBC URL and credentials into Spring automatically, and Flyway migrations run against a real PostgreSQL.
Core Concepts
The Testing Pyramid for Spring
See unit testing and integration testing.
Unit Tests Without Spring
Services using constructor injection (dependency injection) are ordinary Java objects: instantiate them with in-memory fakes or Mockito mocks. These tests are the fastest and should cover most business rules.
Test Slices
By default, @DataJpaTest tries to replace your datasource with an embedded database. Pair it with Testcontainers and @AutoConfigureTestDatabase(replace = NONE) (or @ServiceConnection) to test real SQL behavior. See Spring Data JPA.
@SpringBootTest
Loads the complete application context:
webEnvironment = MOCK(default): a mock servlet environment, used withMockMvcvia@AutoConfigureMockMvc.RANDOM_PORT: a real embedded server, used withTestRestTemplateorWebTestClient, orRestClientpointed at the random port.properties = {...}and@ActiveProfiles("test")for test configuration.
Testcontainers and @ServiceConnection
Testcontainers starts real dependencies in Docker for tests (PostgreSQL, MySQL, Kafka, Redis, LocalStack, Elasticsearch). Spring Boot's @ServiceConnection automatically configures connection properties from the container, replacing manual @DynamicPropertySource wiring. Containers can be shared across test classes (static containers, or a shared configuration class imported with @Import) to avoid restart costs. Spring Boot can also run the same containers during local development (spring-boot-testcontainers with SpringApplication.from(...).with(...)).
Mocking Beans
@MockitoBean replaces a bean in the context with a Mockito mock, and @MockitoSpyBean wraps a real bean. Use them for boundaries you don't want to hit (payment gateways, email) or in slices to isolate the layer under test. Each distinct combination of mocked beans produces a different context cache key, so many variations lead to many context startups and slow suites.
Context Caching
Spring caches application contexts across test classes with identical configuration. Suites stay fast when tests share configuration. They slow down with many unique @MockitoBean sets, @DirtiesContext, differing properties, or profiles. Consolidate common setups in base classes or shared test configurations.
Best Practices
Test Behavior at the Right Layer
Validate business rules in unit tests, HTTP contracts (status codes, validation errors, JSON) in @WebMvcTest, queries and mappings in @DataJpaTest against a real database, and critical flows end to end with @SpringBootTest plus Testcontainers.
Use Real Databases for Persistence Tests
H2 differs from PostgreSQL and MySQL in SQL dialect, types, constraints, and locking. Testing against the same engine and version as production catches the bugs that matter.
Keep Tests Independent
Reset state between tests: @DataJpaTest rolls back by default, and for @SpringBootTest with a real server (where transactions don't roll back), clean tables or use unique data per test. Avoid ordering dependencies.
Test Security Rules Explicitly
Include tests for unauthenticated, unauthorized, and authorized access to sensitive endpoints, using spring-security-test helpers. See Spring Security.
Common Mistakes
@SpringBootTest for Everything
Loading the full context, with all beans, auto-configuration, and database connections, just to test a pricing calculation makes suites slow and brittle. Use plain unit tests or slices.
Proliferating Context Configurations
Standardize a few shared test configurations.
Asserting on Mocks Instead of Outcomes
Tests that only verify verify(repo).save(any()) pass even when the saved data is wrong. Prefer asserting observable outcomes (database state, responses, published events), and keep mock verification for true side-effect boundaries. See mocking and stubbing.
FAQ
What's the difference between @WebMvcTest and @SpringBootTest?
@WebMvcTest loads only the web layer (controllers, filters, JSON, security), with services mocked, so it's fast and focused on HTTP behavior. @SpringBootTest loads the entire application context, which is used for integration tests across layers, often with real infrastructure.
Should I use H2 for tests?
Prefer Testcontainers with the same database you run in production. H2's compatibility modes miss dialect differences, constraint behavior, JSON and array types, and locking semantics. Testcontainers with container reuse keeps runs fast enough for most teams.
What replaced @MockBean?
Spring Framework 6.2 / Spring Boot 3.4 introduced @MockitoBean and @MockitoSpyBean in the core test framework. @MockBean and @SpyBean are deprecated in Boot 3.4 and removed in later versions.
How do I speed up a slow Spring test suite?
Push logic into plain unit tests, use slices instead of full contexts, reduce distinct context configurations (so caching works), share Testcontainers across classes, run tests in parallel where safe, and profile which test classes start new contexts.
Related Topics
- Spring Boot — The framework overview
- Integration Testing — Testing with real dependencies
- Unit Testing — Fast, isolated tests
- Mocking & Stubbing — When and how to fake dependencies
- Spring Data JPA — Repository testing with @DataJpaTest
- Spring Security — Testing authorization rules