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

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

Модуль 5. Spring
14 уровень , 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 {
        // Создаем объект 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 не будут работать.
  • Проверяйте сообщения об ошибках. Когда что-то идет не так, изучите стек-трейс — часто проблема лежит в мелочах.

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

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ