Інтеграційні тести перевіряють взаємодію між кількома компонентами застосунку. Якщо 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 {
// Cтворюємо об'єкт 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 не працюватимуть. - Перевіряй повідомлення про помилки. Коли щось йде не так, вивчи stack-trace — часто проблема в дрібницях.
Тепер ви озброєні всіма необхідними знаннями для написання інтеграційних тестів REST API. Ви можете використовувати цю практику в реальних проєктах, щоб переконатися, що ваш застосунок відповідає вимогам і поводиться як задумано. Приклад вище також стане в нагоді при підготовці до співбесід, де часто просять навести приклади тестів.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ