Testing with @SpringBootTest
Write integration tests that load the full Spring application context, and controller tests with MockMvc that exercise the web layer without starting a real server.
Introduction
A Spring Boot application is only as trustworthy as its tests. Boot ships with the spring-boot-starter-test dependency, which bundles JUnit 5, Mockito, AssertJ, and Spring's own testing support in a single starter. In this lesson you will write a plain unit test for a service, then an integration test with @SpringBootTest that loads the real application context, and finally a controller test with MockMvc that exercises the web layer without starting an actual HTTP server.
- The difference between unit tests, slice tests, and full integration tests.
- How to unit test a service with Mockito.
- How @SpringBootTest loads the application context.
- How to test controllers with MockMvc.
- How to run tests against a dedicated test profile.
Testing Levels in Spring Boot
Spring Boot tests generally fall into three levels, each trading off speed against realism. Unit tests are the fastest and should make up the bulk of your test suite; full integration tests are the slowest and should be used more sparingly, for the flows that matter most.
| Level | What It Loads | Speed |
|---|---|---|
| Unit test | Nothing from Spring - plain objects and mocks | Very fast |
| Slice test (@WebMvcTest, @DataJpaTest) | Only the relevant layer (web or JPA) | Fast |
| @SpringBootTest | The entire application context | Slower |
Unit Testing a Service
A pure unit test does not involve Spring at all. Because TaskService uses constructor injection, you can create it directly with new and pass in a mocked repository.
@ExtendWith(MockitoExtension.class)class TaskServiceTest {
@Mock private TaskRepository taskRepository;
private TaskService taskService;
@BeforeEach void setUp() { taskService = new TaskService(taskRepository); }
@Test void findAll_returnsTasksFromRepository() { Task task = new Task(1L, "Write lesson", false); when(taskRepository.findAll()).thenReturn(List.of(task));
List<Task> result = taskService.findAll();
assertThat(result).hasSize(1); assertThat(result.get(0).getTitle()).isEqualTo("Write lesson"); }}Click Run to see what this code prints.
@SpringBootTest
@SpringBootTest boots the entire Spring application context, exactly as it would start in production, and lets you inject real beans into the test. It is the closest a test gets to running the whole application, which makes it slower but valuable for verifying that everything wires together correctly.
@SpringBootTestclass TaskApplicationTests {
@Autowired private TaskService taskService;
@Test void contextLoads() { assertThat(taskService).isNotNull(); }}A test that only checks that the application context starts successfully looks trivial, but it catches an entire category of bugs - missing beans, misconfigured properties, and broken wiring - the moment they are introduced.
Testing Controllers with MockMvc
MockMvc lets you send simulated HTTP requests directly to your controllers without starting a real servlet container, which makes controller tests fast while still exercising request mapping, validation, and JSON serialization.
@SpringBootTest@AutoConfigureMockMvcclass TaskControllerTest {
@Autowired private MockMvc mockMvc;
@MockBean private TaskService taskService;
@Test void getAllTasks_returnsJsonArray() throws Exception { when(taskService.findAll()) .thenReturn(List.of(new Task(1L, "Write lesson", false)));
mockMvc.perform(get("/api/tasks")) .andExpect(status().isOk()) .andExpect(jsonPath("$[0].title").value("Write lesson")); }}Click Run to see what this code prints.
@MockBean replaces the real TaskService bean in the application context with a Mockito mock, so the test exercises the actual controller and its request mapping while stubbing out the business logic underneath.
Using a Test Profile
Tests often need different configuration than production - an in-memory database instead of a real one, for example. Create an application-test.properties file and activate it with @ActiveProfiles so tests never touch real infrastructure.
# src/test/resources/application-test.propertiesspring.datasource.url=jdbc:h2:mem:testdbspring.jpa.hibernate.ddl-auto=create-drop@SpringBootTest@ActiveProfiles("test")class TaskRepositoryIntegrationTest {
@Autowired private TaskRepository taskRepository;
@Test void save_persistsTask() { Task saved = taskRepository.save(new Task(null, "Integration test", false)); assertThat(saved.getId()).isNotNull(); }}Common Mistakes
- Reaching for @SpringBootTest for every test, which makes the suite slow when most cases only need a plain unit test.
- Letting tests run against the real production database instead of a dedicated test profile.
- Forgetting @AutoConfigureMockMvc, which leaves MockMvc unavailable to autowire.
- Not resetting or scoping mocks between tests, causing state to leak from one test into the next.
- Testing implementation details instead of behavior, which makes tests brittle during refactors.
Best Practices
- Write mostly fast unit tests, with a smaller number of @SpringBootTest integration tests for critical paths.
- Use @WebMvcTest for controller-only slice tests when you do not need the full context.
- Always run tests against an isolated test profile and an in-memory or containerized database.
- Use @MockBean to isolate the layer under test from its collaborators.
- Name tests to describe behavior, such as findAll_returnsTasksFromRepository, not just methodName_test.
Frequently Asked Questions
@Mock is plain Mockito and works in tests with no Spring context at all. @MockBean is Spring Boot specific - it replaces a real bean in the Spring application context with a mock, which is what you need inside a @SpringBootTest.
No. Most teams use an in-memory database like H2 for tests, or a containerized database via Testcontainers for higher-fidelity integration tests. Either way, tests should never depend on a shared production database.
It starts the entire application context - all your beans, auto-configuration, and (unless mocked) real infrastructure connections - which naturally takes longer than a plain unit test that instantiates a single class.
Key Takeaways
- spring-boot-starter-test bundles JUnit 5, Mockito, AssertJ, and Spring test support.
- Unit tests should form the bulk of your suite; @SpringBootTest is for full integration checks.
- MockMvc tests the web layer without starting a real HTTP server.
- @MockBean swaps a real bean for a mock inside the Spring test context.
- Use a dedicated test profile so tests never touch production infrastructure.
Summary
Spring Boot gives you everything needed to test at every level: fast unit tests for isolated logic, MockMvc for the web layer, and @SpringBootTest for full integration coverage. A healthy test suite leans heavily on the fast layers and uses full context tests sparingly, for the paths that matter most.