Testing Spring Boot Applications
Objective
Spring Boot layers auto-configuration and component scanning on top of the Spring TestContext framework. The single annotation @SpringBootTest bootstraps a full application context for a test: it locates the Boot configuration, scans the test class's package and subpackages for beans, applies auto-configuration, and then autowires whatever the test asks for. This turns the multi-annotation setup of plain Spring (@ExtendWith(SpringExtension.class) + @ContextConfiguration) into one opinionated entry point for integration testing a Boot application.
Use Cases
- Integration-testing a feature end to end with the real bean graph — controllers, services, repositories — loaded exactly as the app would wire them.
- Testing behavior that spans several collaborating beans (publishing a Spring event and asserting listeners reacted) where mocking everything would defeat the purpose.
- Migrating a plain Spring test to Boot: dropping
@ContextConfigurationin favor of@SpringBootTest's component scan. - Pulling extra fixture beans into the context for a test via
@Import(SomeBuilder.class)without polluting production configuration. - Bridging legacy XML wiring into a Boot test with
@ImportResourcewhile migrating configuration to annotations/Java config.
Deep Dive
@SpringBootTest = context + component scan + auto-config
@SpringBootTest starts the application context the way Spring Boot would at runtime. It searches the current test class's package and its subpackages for bean definitions, so beans there are discovered and can be autowired directly into the test:
java@SpringBootTest
@Import(FlightBuilder.class) // brings in the Flight + Country fixture beans
public class FlightTest {
@Autowired
private Flight flight; // discovered/imported and injected
@Autowired
private RegistrationManager registrationManager; // found by component scan
@Test
void testFlightPassengersRegistration() {
for (Passenger passenger : flight.getPassengers()) {
assertFalse(passenger.isRegistered());
registrationManager.getApplicationContext()
.publishEvent(new PassengerRegistrationEvent(passenger));
}
for (Passenger passenger : flight.getPassengers()) {
assertTrue(passenger.isRegistered());
}
}
}No @ExtendWith is written here: @SpringBootTest is meta-annotated with @ExtendWith(SpringExtension.class), so the extension is applied transitively.
Mixing in existing configuration
While migrating, a Boot test can still consume beans defined the old way. @ImportResource pulls in an XML context; @EnableAutoConfiguration (implied by @SpringBootTest, but shown here explicitly in the book's migration step) triggers Boot's auto-configuration:
java@SpringBootTest
@EnableAutoConfiguration
@ImportResource("classpath:application-context.xml") // legacy XML beans
class RegistrationTest {
@Autowired
private RegistrationManager registrationManager; // still autowired
}This is the "gentle migration" path: keep the XML beans working under Boot, then move them to Java config incrementally.
Book vs. today: test slices instead of always loading everything
The book (Spring Boot 2.x, 2020) reaches for
@SpringBootTestfor its examples, loading the whole context each time. Spring Boot also ships test slices — annotations that load only the beans for one layer, making tests much faster and more focused:
java@WebMvcTest(CountryController.class) // only the web layer (controllers, MVC infra)
class CountryControllerTest { /* no service/repository beans loaded */ }
@DataJpaTest // only JPA repositories + an embedded DB
class PassengerRepositoryTest { /* controllers/services not loaded */ }Reach for
@SpringBootTestwhen you genuinely need the full context (true end-to-end integration); reach for a slice when you're testing one layer. The book's approach still works — it's just heavier than necessary for single-layer tests.
Trade-offs
- Full-context tests are the slowest kind —
@SpringBootTestboots the entire application, so a suite of them is far slower than sliced or unit tests; the context is cached and reused across classes with identical configuration, but a full context is still expensive to build the first time. - Component scan depends on package placement —
@SpringBootTestscans the test class's package and subpackages, so a bean defined outside that tree is silently not found and autowiring fails:
java@Autowired
private RegistrationManager registrationManager; // fails if the bean lives outside the scanned package tree- Convenience hides what's loaded — because one annotation pulls in auto-configuration and a full scan, it's easy to depend on a bean you didn't realize was there, making the test's true dependency surface larger and more fragile than a slice that loads a known, minimal set.
@MockBeanhas moved on — the book replaces real beans with@MockBean; since Spring Boot 3.4 / Spring Framework 6.2,@MockBeanand@SpyBeanare deprecated in favor of@MockitoBeanand@MockitoSpyBean(now part of the framework, not Boot):
java// book (Boot 2.x) → today (Boot 3.4+ / Framework 6.2+)
// @MockBean @MockitoBean
private CountryRepository countryRepository;