Why Testing Spring Boot Applications Needs Its Own Strategy
Spring Boot applications wire together web layers, persistence, security, and messaging, so a single unit-test mindset misses failures that only appear once those pieces interact. Spring Boot's test starters (spring-boot-starter-test) bundle JUnit 5, Mockito, AssertJ, and Spring's test context framework so you can choose the right altitude of test — pure unit, slice, or full integration — for each concern.
Cricket analogy: Just as a bowler practices yorkers in the nets but still needs a full net session with a wicketkeeper and slip cordon to test how a plan plays out live, Spring Boot needs both isolated unit tests and full-context integration tests like a Kohli-vs-Bumrah net simulation.
Unit Testing Services with Mockito
For pure business logic in @Service classes, avoid loading the Spring context entirely — use plain JUnit 5 with Mockito's @Mock and @InjectMocks annotations so tests run in milliseconds. MockitoExtension (registered via @ExtendWith(MockitoExtension.class)) creates mocks for dependencies like a repository, and @InjectMocks constructs the service under test, injecting those mocks through its constructor.
Cricket analogy: A batting coach isolates a player in a bowling machine session with no fielders or crowd noise, just like @ExtendWith(MockitoExtension.class) isolates a service from the rest of the Spring context.
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private OrderRepository orderRepository;
@InjectMocks
private OrderService orderService;
@Test
void shouldThrowWhenOrderNotFound() {
when(orderRepository.findById(1L)).thenReturn(Optional.empty());
assertThrows(OrderNotFoundException.class,
() -> orderService.getOrder(1L));
verify(orderRepository).findById(1L);
}
}Slice Testing the Web Layer with @WebMvcTest
@WebMvcTest(OrderController.class) loads only the MVC infrastructure — the controller, @ControllerAdvice, and Jackson configuration — without booting the full application context, service beans, or database. You mock service dependencies with @MockBean and drive requests through MockMvc, asserting on HTTP status, JSON body via jsonPath(), and headers, which makes these tests fast and focused on request/response contracts.
Cricket analogy: A fielding coach runs a drill focused purely on slip-catching technique without setting up the whole eleven, just as @WebMvcTest loads only the web layer instead of the entire application.
Integration Testing with @SpringBootTest and Testcontainers
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) boots the entire application context and a real embedded servlet container, letting you use TestRestTemplate or WebTestClient to exercise the app exactly as a client would. Pairing this with Testcontainers — spinning up a real PostgreSQL or Kafka container via @Container and @DynamicPropertySource — avoids the false confidence of H2-in-memory tests behaving differently from your production database's SQL dialect and constraint behavior.
Cricket analogy: A team plays a full day-night practice match under real floodlights and a genuine pitch rather than the nets, the way Testcontainers gives you a real database instead of an in-memory stand-in.
Use @DataJpaTest for repository-only slices (it configures an embedded database and rolls back each test in a transaction) and reserve full @SpringBootTest runs for scenarios that genuinely need the whole context, since each context boot is cached by Spring's TestContext framework but is still far slower than a slice test.
Relying on H2 for integration tests when production runs PostgreSQL is a common trap — H2's SQL dialect, JSON column support, and constraint enforcement differ enough that tests can pass locally and fail in production. Prefer Testcontainers with the real database image whenever the query logic matters.
- Layer your tests: fast Mockito unit tests for logic,
@WebMvcTest/@DataJpaTestslices for one layer,@SpringBootTestfor full-context integration. @ExtendWith(MockitoExtension.class)with@Mock/@InjectMockstests services without booting Spring at all.@WebMvcTestloads only MVC infrastructure and usesMockMvcplus@MockBeanto test controllers in isolation.@SpringBootTest(webEnvironment = RANDOM_PORT)boots a real embedded server for true end-to-end HTTP testing.- Testcontainers gives integration tests a real database/broker instance, avoiding H2-vs-production dialect mismatches.
- Spring's
TestContextframework caches application contexts across test classes with identical configuration to reduce boot overhead. @DataJpaTestauto-configures an embedded/test datasource and wraps each test in a rolled-back transaction by default.
Practice what you learned
1. Which annotation loads only the MVC layer of a Spring Boot application for testing a controller?
2. What is the primary reason to prefer Testcontainers over H2 for integration tests?
3. In a Mockito-based unit test, what does @InjectMocks do?
4. Which annotation would you use to test a Spring Data JPA repository against a transactional, rolled-back test database?
5. What does webEnvironment = WebEnvironment.RANDOM_PORT do in @SpringBootTest?
Was this page helpful?
You May Also Like
Spring Boot with Docker
How to containerize Spring Boot applications using Dockerfiles, layered JARs, Cloud Native Buildpacks, and Docker Compose for local development.
Building Microservices with Spring Boot
How to design, discover, secure, and make resilient a set of independently deployable Spring Boot services using Spring Cloud.
Spring Boot Quick Reference
A condensed cheat sheet of the most-used Spring Boot annotations, configuration patterns, Actuator endpoints, and CLI commands.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics