JavaRush /Курси /Модуль 5. Spring /Лекція 137: Практика: написання інтеграційних тестів для ...

Лекція 137: Практика: написання інтеграційних тестів для REST API

Модуль 5. Spring
Рівень 9 , Лекція 6
Відкрита

Інтеграційні тести перевіряють взаємодію між кількома компонентами застосунку. Якщо Unit-тести досліджують кожен компонент в ізоляції, то тут, навпаки, нас цікавить, чи працюють вони коректно разом. Наприклад, чи можемо ми отримати дані з бази через контролер, чи правильно обробляються запити і чи коректно сервіси спілкуються один з одним.

Чому вони важливі?

  • Ви перевіряєте реальну взаємодію компонентів. REST API без працюючого сервісного шару — це просто декорація, погодьтесь.
  • Впевненість у функціональності: інтеграційні тести допомагають зрозуміти, що ваш застосунок виконує свої задачі.
  • Виявлення помилок конфігурації: часто помилки виникають не в коді, а в налаштуваннях компонентів (наприклад, підключення до бази даних).

Як підготуватися до інтеграційних тестів?

Оскільки ми будемо взаємодіяти з усіма шарами нашого застосунку, нам потрібно налаштувати наступні елементи:

  1. Тестова база даних. Для ізоляції використовуємо вбудовану базу даних (наприклад, H2).
  2. Запуск усього контексту Spring. Завдання інтеграційних тестів — протестувати застосунок так, ніби він виконується в реальному середовищі.
  3. Відповідні анотації. Ми використовуємо анотацію @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-методи...
}

Тестовий клас

Для інтеграційного тестування створимо тестовий клас. Ми будемо перевіряти взаємодію між контролером і репозиторієм.

Кроки:

  1. Використовуємо анотацію @SpringBootTest для запуску контексту Spring.
  2. Використовуємо 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
    }
}
  1. Анотація @SpringBootTest завантажує весь контекст застосунку, включаючи базу даних.
  2. Анотація @AutoConfigureMockMvc налаштовує MockMvc для імітації HTTP-запитів.
  3. У тесті shouldCreateUser ми відправляємо POST-запит для створення нового користувача і перевіряємо, що сервер відповідає з кодом 201 і повертає коректні дані.
  4. У тесті shouldRetrieveUserById перевіряємо, що існуючого користувача можна знайти за його ID.
  5. У тесті shouldReturnNotFoundWhenUserDoesNotExist перевіряємо, що запит до неіснуючого ресурсу повертає статус 404.

Основні помилки і лайфхаки

Якщо ти коли-небудь писав тести, то ймовірно знаєш, що "Якщо щось може піти не так, воно обов'язково піде не так". Ось як мінімізувати проблеми:

  • Помилка: база даних містить "сміття". Якщо попередні тести залишають дані в базі, це може вплинути на результати наступних тестів. Завжди очищай базу перед кожним тестом (як у методі cleanDatabase в прикладі).
  • Помилка конфігурації бази даних. Переконайся, що тестова база дійсно використовується для тестів, а не твоя основна база. Перевір application.properties!
  • Проблеми з MockMvc. Якщо ти забув анотацію @AutoConfigureMockMvc, твої тести з використанням MockMvc не працюватимуть.
  • Перевіряй повідомлення про помилки. Коли щось йде не так, вивчи stack-trace — часто проблема в дрібницях.

Тепер ви озброєні всіма необхідними знаннями для написання інтеграційних тестів REST API. Ви можете використовувати цю практику в реальних проєктах, щоб переконатися, що ваш застосунок відповідає вимогам і поводиться як задумано. Приклад вище також стане в нагоді при підготовці до співбесід, де часто просять навести приклади тестів.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ