LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2420 min read

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.

What You Will Learn
  • 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.

LevelWhat It LoadsSpeed
Unit testNothing from Spring - plain objects and mocksVery fast
Slice test (@WebMvcTest, @DataJpaTest)Only the relevant layer (web or JPA)Fast
@SpringBootTestThe entire application contextSlower

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");
}
}
Test Run

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.

@SpringBootTest
class TaskApplicationTests {
@Autowired
private TaskService taskService;
@Test
void contextLoads() {
assertThat(taskService).isNotNull();
}
}
A Context-Loads Test Is Not Wasted

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
@AutoConfigureMockMvc
class 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"));
}
}
Test Run

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.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.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

Avoid These 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.

Next Lesson →

Logging in Spring Boot