Integration tests verify how different parts of your app (for example, controllers, services, and repositories) interact and whether they work correctly under a shared configuration. They help make sure components "understand" each other and operate in sync. Unlike unit tests, integration tests can use real databases, web servers, and other live dependencies.
Why do integration tests matter?
- Checking app integrity: integration tests ensure all parts of the app work together without breaking.
- Realistic environment: these tests are close to real-world conditions, including interaction with databases, services, and APIs.
- Catching interaction bugs: even if individual components work fine, their interaction can have bugs that integration tests reveal.
Differences between Unit and integration tests
| Characteristic | Unit test | Integration test |
|---|---|---|
| Goal | Test a single method | Test interaction between components |
| Environment | Isolated, with mocks | Real environment |
| Data used | Stubs (mocked data) | Real or test database |
| Performance | Very fast | Slower |
| Use case | Local logic checks | End-to-end functionality checks |
Why use Spring Boot Test?
Spring Boot gives you all the tools you need to write both Unit and integration tests. The main superpower for integration testing here is the @SpringBootTest annotation. It boots the entire Spring context, letting you test the app in conditions that are as close to real as possible.
Benefits of using Spring Boot Test:
- Automatic loading of the application context.
- Connecting a test database via configuration.
- Easy testing of REST APIs, component interactions, and the database.
- Flexibility to test specific parts of the app if you filter the context.
Annotation @SpringBootTest
To write integration tests in Spring Boot we use the @SpringBootTest annotation. It loads the full application context including controllers, services, repositories, configurations and even an embedded web server (if needed).
Here's a minimal example of a test class:
@SpringBootTest
class IntegrationTestExample {
@Autowired
private SomeService someService;
@Test
void testServiceMethod() {
String result = someService.performAction();
assertEquals("Expected Result", result);
}
}
Important notes:
- Using
@SpringBootTestloads the whole app context, which makes tests slower than Unit tests. So only use it where it's really needed. - If you want to test only part of the context, you can limit what gets loaded, e.g. with
@WebMvcTestor@DataJpaTest.
Configuring the test environment
Sometimes integration tests need a temporary database or other components that shouldn't affect the real environment. Spring Boot makes this easy.
1. Using an in-memory database
For testing repositories you can use H2 — an in-memory database:
# application-test.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
Then in tests we tell Spring to use that test profile:
@SpringBootTest
@ActiveProfiles("test") // Indicates use of application-test.properties
class RepositoryIntegrationTest {
// Tests
}
2. Automatic data cleanup
For DB tests it's useful to automatically clear data after each test. Spring Boot helps with that via @Transactional:
@SpringBootTest
@Transactional // Rolls back all changes after each test
class DatabaseIntegrationTest {
// Tests
}
3. MockMVC and testing REST APIs
To test REST API interactions we use MockMvc, which lets you simulate HTTP requests and inspect responses.
Writing an integration test: Practical example
Let's say we have a task management app. We'll test a REST API that talks to the database to create and fetch tasks.
1. Controller example
@RestController
@RequestMapping("/api/tasks")
public class TaskController {
@Autowired
private TaskService taskService;
@PostMapping
public ResponseEntity<Task> createTask(@RequestBody Task task) {
return ResponseEntity.status(HttpStatus.CREATED).body(taskService.createTask(task));
}
@GetMapping("/{id}")
public ResponseEntity<Task> getTask(@PathVariable Long id) {
return ResponseEntity.ok(taskService.getTaskById(id));
}
}
2. Service and repository
@Service
public class TaskService {
@Autowired
private TaskRepository taskRepository;
public Task createTask(Task task) {
return taskRepository.save(task);
}
public Task getTaskById(Long id) {
return taskRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("Task not found"));
}
}
public interface TaskRepository extends JpaRepository<Task, Long> {}
3. Testing the API with MockMvc
@SpringBootTest
@AutoConfigureMockMvc
class TaskControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
void testCreateAndRetrieveTask() throws Exception {
// JSON to create a task
String newTaskJson = "{ \"title\": \"Learn Testing\", \"description\": \"Understand integration tests\" }";
// Create the task
MvcResult result = mockMvc.perform(post("/api/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content(newTaskJson))
.andExpect(status().isCreated())
.andReturn();
// Get ID of the created task
Long taskId = JsonPath.read(result.getResponse().getContentAsString(), "$.id");
// Verify the task exists
mockMvc.perform(get("/api/tasks/" + taskId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.title").value("Learn Testing"))
.andExpect(jsonPath("$.description").value("Understand integration tests"));
}
}
Useful annotations for integration tests
@SpringBootTest: boots the full application context.@AutoConfigureMockMvc: auto-configures MockMvc for REST API testing.@Transactional: ensures DB changes are rolled back after the test.@TestPropertySource: points to separate property files for tests.@ActiveProfiles: selects a specific configuration profile.
Common mistakes when writing integration tests
- Overloading the context: sprinkling
@SpringBootTesteverywhere. If you only need to test the REST layer or repositories, prefer specialized annotations (@WebMvcTest,@DataJpaTest). - No test database: use in-memory DBs or Testcontainers so you don't pollute the real DB.
- Non-isolated tests: make tests independent so their execution order doesn't affect results.
Integration tests are a key tool to ensure your app works correctly as a whole. Once you get comfortable with them, you'll feel much more confident that your code won't break after every change.
GO TO FULL VERSION