Integration tests verify how multiple components of the application interact. If Unit tests study each component in isolation, here we want to know whether they work correctly together. For example, can we fetch data from the database via a controller, are requests handled properly, and do services communicate with each other correctly.
Why are they important?
- You check the actual interaction between components. A REST API without a working service layer is just decoration, right?
- Confidence in functionality: integration tests help you make sure your app does what it's supposed to do.
- Catching configuration issues: often problems aren't in the code but in component settings (for example, database connection).
How to prepare for integration tests?
Since we'll be interacting with all layers of our app, we need to set up the following:
- Test database. For isolation we'll use an in-memory database (for example, H2).
- Start the whole Spring context. The point of integration tests is to test the app as if it's running in a real environment.
- Appropriate annotations. We use the
@SpringBootTestannotation to load the entire application context.
Environment setup
Creating a test database
In application.properties we'll add settings for connecting to H2. This lets our tests run without depending on a real database:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
These settings will create a database on the fly that will be dropped after the tests finish.
Writing integration tests
Let's consider an app that works with the User entity. We have a REST API that allows CRUD operations (create, read, update, delete) for users.
Example of our User entity
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Getters and setters...
}
Repository for database access
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Controller to handle requests
@RestController
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User savedUser = userRepository.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userRepository.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// Other CRUD methods...
}
Test class
For integration testing we'll create a test class. We'll check interaction between the controller and the repository.
Steps:
- Use the
@SpringBootTestannotation to launch the Spring context. - Use
MockMvcto simulate HTTP requests and verify responses.
@SpringBootTest
@AutoConfigureMockMvc
class UserIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserRepository userRepository;
@BeforeEach
void cleanDatabase() {
userRepository.deleteAll();
}
@Test
void shouldCreateUser() throws Exception {
// Create User object to send
User user = new User();
user.setName("John Doe");
user.setEmail("john.doe@example.com");
// Perform POST request
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated()) // Check that status is 201 Created
.andExpect(jsonPath("$.id").exists()) // Check that response contains ID
.andExpect(jsonPath("$.name").value("John Doe")) // Check name
.andExpect(jsonPath("$.email").value("john.doe@example.com")); // Check email
}
@Test
void shouldRetrieveUserById() throws Exception {
// Save a user to the database
User user = new User();
user.setName("Jane Doe");
user.setEmail("jane.doe@example.com");
user = userRepository.save(user);
// Perform GET request
mockMvc.perform(get("/users/{id}", user.getId())
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) // Check that status is 200 OK
.andExpect(jsonPath("$.id").value(user.getId()))
.andExpect(jsonPath("$.name").value("Jane Doe"))
.andExpect(jsonPath("$.email").value("jane.doe@example.com"));
}
@Test
void shouldReturnNotFoundWhenUserDoesNotExist() throws Exception {
// Perform GET request with a non-existing ID
mockMvc.perform(get("/users/{id}", 999)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound()); // Check that status is 404
}
}
- The
@SpringBootTestannotation loads the entire application context, including the database. - The
@AutoConfigureMockMvcannotation sets upMockMvcto simulate HTTP requests. - In the
shouldCreateUsertest we send a POST request to create a new user and verify the server responds with code 201 and returns correct data. - In the
shouldRetrieveUserByIdtest we check that an existing user can be found by its ID. - In the
shouldReturnNotFoundWhenUserDoesNotExisttest we verify that a request to a non-existing resource returns status 404.
Common mistakes and tips
If you've ever written tests, you probably know that "If something can go wrong, it will". Here's how to minimize issues:
- Problem: the database contains "junk". If previous tests leave data in the database, it can affect the results of following tests. Always clean the database before each test (like in the
cleanDatabasemethod in the example). - Database configuration error. Make sure the test database is actually used for tests and not your main database. Check
application.properties! - Problems with MockMvc. If you forgot the
@AutoConfigureMockMvcannotation, your MockMvc-based tests won't work. - Check error messages. When something goes wrong, study the stack trace — often the issue is a tiny detail.
Now you're equipped with the essentials for writing integration tests for REST APIs. You can use this practice in real projects to make sure your app meets requirements and behaves as intended. The example above is also useful for interview prep, where you're often asked to give examples of tests.
GO TO FULL VERSION