Интеграционные тесты проверяют взаимодействие между несколькими компонентами приложения. Если Unit-тесты изучают каждый компонент в изоляции, то здесь, напротив, нас интересует, работают ли они корректно вместе. Например, можем ли мы получить данные из базы через контроллер, правильно ли обрабатываются запросы и корректно ли сервисы общаются друг с другом.
Почему они важны?
- Вы проверяете реальное взаимодействие компонентов. REST API без работающего сервисного слоя — это просто декорация, согласитесь.
- Уверенность в функциональности: интеграционные тесты помогают понять, что ваше приложение выполняет свои задачи.
- Выявление ошибок конфигурации: часто ошибки возникают не в коде, а в настройках компонентов (например, подключения к базе данных).
Как подготовиться к интеграционным тестам?
Поскольку мы будем взаимодействовать со всеми слоями нашего приложения, нам понадобится настроить следующие элементы:
- Тестовая база данных. Для изоляции используем встроенную базу данных (например, H2).
- Запуск всего контекста Spring. Задача интеграционных тестов — протестировать приложение, как если бы оно выполнялось в реальной среде.
- Соответствующие аннотации. Мы используем аннотацию
@SpringBootTest, чтобы загрузить весь контекст приложения.
Подготовка окружения
Создание тестовой базы данных
В файле application.properties добавим настройки для подключения к H2. Это позволит выполнять наши тесты без зависимости от реальной базы данных:
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
Эти настройки создадут базу данных "на лету", которая будет удаляться после завершения тестов.
Написание интеграционных тестов
Рассмотрим приложение, работающее с сущностью User. У нас есть REST API, которое позволяет выполнять CRUD-операции (создание, чтение, обновление и удаление пользователей).
Пример нашей сущности User
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Геттеры и сеттеры...
}
Репозиторий для работы с базой данных
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Контроллер для обработки запросов
@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());
}
// Остальные CRUD методы...
}
Тестовый класс
Для интеграционного тестирования создадим тестовый класс. Мы будем проверять взаимодействие между контроллером и репозиторием.
Шаги:
- Используем аннотацию
@SpringBootTestдля запуска контекста Spring. - Используем
MockMvcдля имитации HTTP-запросов и проверки ответов.
@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 {
// Создаем объект User для отправки
User user = new User();
user.setName("John Doe");
user.setEmail("john.doe@example.com");
// Выполняем POST-запрос
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated()) // Проверяем, что статус 201 Created
.andExpect(jsonPath("$.id").exists()) // Проверяем, что в ответе есть ID
.andExpect(jsonPath("$.name").value("John Doe")) // Проверяем имя
.andExpect(jsonPath("$.email").value("john.doe@example.com")); // Проверяем email
}
@Test
void shouldRetrieveUserById() throws Exception {
// Сохраняем пользователя в базу данных
User user = new User();
user.setName("Jane Doe");
user.setEmail("jane.doe@example.com");
user = userRepository.save(user);
// Выполняем GET-запрос
mockMvc.perform(get("/users/{id}", user.getId())
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) // Проверяем, что статус 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 {
// Выполняем GET-запрос с несуществующим ID
mockMvc.perform(get("/users/{id}", 999)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound()); // Проверяем, что статус 404
}
}
- Аннотация
@SpringBootTestзагружает весь контекст приложения, включая базу данных. - Аннотация
@AutoConfigureMockMvcнастраиваетMockMvcдля имитации HTTP-запросов. - В тесте
shouldCreateUserмы отправляем POST-запрос для создания нового пользователя и проверяем, что сервер отвечает с кодом 201 и возвращает корректные данные. - В тесте
shouldRetrieveUserByIdпроверяем, что существующий пользователь может быть найден по его ID. - В тесте
shouldReturnNotFoundWhenUserDoesNotExistпроверяем, что запрос к несуществующему ресурсу возвращает статус 404.
Основные ошибки и лайфхаки
Если вы когда-либо писали тесты, то вероятно знаете, что "Если что-то может пойти не так, оно обязательно пойдет не так". Вот как минимизировать проблемы:
- Ошибка: база данных содержит "мусор". Если предыдущие тесты оставляют данные в базе, это может повлиять на результаты следующих тестов. Всегда очищайте базу перед каждым тестом (как в методе
cleanDatabaseв примере). - Ошибка конфигурации базы данных. Убедитесь, что тестовая база действительно используется для тестов, а не ваша основная база. Проверьте
application.properties! - Проблемы с MockMvc. Если вы забыли аннотацию
@AutoConfigureMockMvc, ваши тесты с использованием MockMvc не будут работать. - Проверяйте сообщения об ошибках. Когда что-то идет не так, изучите стек-трейс — часто проблема лежит в мелочах.
Теперь вы вооружены всеми необходимыми знаниями для написания интеграционных тестов REST API. Вы можете использовать эту практику в реальных проектах, чтобы убедиться, что ваше приложение отвечает требованиям и ведет себя как задуманно. Пример выше также пригодится для подготовки к собеседованиям, где часто просят привести примеры тестов.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ