Stage 11: Spring Boot, lesson 6 of 8

Testing Spring Boot apps

Intermediate3 min read@since 17Code runs on your Java 25
Explain it forThe essentials plus production detail and pitfalls.

The testing pyramid, from many fast tests to a few slow ones:

  • Unit tests: plain JUnit 5 and Mockito, no Spring context. Constructor injection makes these easy.
  • Slice tests load a single layer: @WebMvcTest (controllers with MockMvc), @DataJpaTest (repositories with a test database), @JsonTest.
  • Integration tests: @SpringBootTest loads the whole application context.

Testcontainers starts real PostgreSQL, Kafka or Redis in Docker for your tests. With @ServiceConnection (Boot 3.1+), no manual URL wiring is needed.

Replace a bean in the Spring context with a mock using @MockitoBean (Spring 6.2+; it replaces the deprecated @MockBean).

Example

Java
@WebMvcTest(CourseController.class)
class CourseControllerTest {
    @Autowired MockMvc mvc;
    @MockitoBean CourseService service;

    @Test
    void returnsCourse() throws Exception {
        when(service.bySlug("spring-boot"))
            .thenReturn(new CourseDto("Spring Boot", 42, Plan.PRO));

        mvc.perform(get("/api/v1/courses/spring-boot"))
           .andExpect(status().isOk())
           .andExpect(jsonPath("$.title").value("Spring Boot"));
    }
}

@SpringBootTest
@Testcontainers
class EnrollmentIT {
    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:17");

    @Autowired EnrollmentService service;

    @Test
    void enrollsStudent() { /* real database, real transactions */ }
}

Common mistake

Using @SpringBootTest for everything. Loading the full context makes the suite slow; most tests should be unit or slice tests.

Under the hood

Spring caches application contexts between tests that share the same configuration. Every different set of @MockitoBeans or properties creates a new context and slows the suite, so prefer slices and shared base classes, and start the database container once per JVM (a static field). H2 standing in for PostgreSQL hides dialect differences; Testcontainers catches them.

Check yourself

Which annotation tests only JPA repositories?

How this connects

Part of Job-ready backend developer, Microservices and production.

Was this lesson helpful?

Finished reading? Mark it complete to track your progress.