100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Java

Testing Spring Boot Applications

A practical guide to layering unit, slice, and integration tests in Spring Boot using JUnit 5, Mockito, MockMvc, and Testcontainers.

Testing & PracticeIntermediate10 min readJul 10, 2026
Analogies

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.

java
@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/@DataJpaTest slices for one layer, @SpringBootTest for full-context integration.
  • @ExtendWith(MockitoExtension.class) with @Mock/@InjectMocks tests services without booting Spring at all.
  • @WebMvcTest loads only MVC infrastructure and uses MockMvc plus @MockBean to 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 TestContext framework caches application contexts across test classes with identical configuration to reduce boot overhead.
  • @DataJpaTest auto-configures an embedded/test datasource and wraps each test in a rolled-back transaction by default.

Practice what you learned

Was this page helpful?

Topics covered

#Java#SpringBootStudyNotes#WebDevelopment#TestingSpringBootApplications#Testing#Spring#Boot#Applications#StudyNotes#SkillVeris