Интеграционные HTTP тесты

Интеграционные тесты фокусируются на проверке взаимодействий между компонентами приложения. В контексте веб-приложений это часто означает тестирование HTTP-запросов и ответов между сервером и клиентом, а также взаимодействия с базой данных. Mocha является одним из популярных инструментов для реализации таких тестов в JavaScript, и в этой главе рассмотрены основные подходы к написанию интеграционных тестов HTTP-запросов с использованием Mocha.

Основы интеграционного тестирования HTTP

Интеграционные тесты для HTTP-запросов обеспечивают проверку работоспособности веб-API, включая правильность ответов на запросы, статусные коды, формат данных и корректность обработки ошибок. Эти тесты проверяют не только работу отдельных функций или модулей, но и то, как компоненты взаимодействуют между собой в реальном приложении.

Для работы с HTTP в Mocha чаще всего используют внешние библиотеки, такие как supertest. Это мощная утилита для тестирования HTTP-API, которая позволяет выполнять запросы к серверу и проверять их ответы.

Настройка окружения

Для создания тестов для HTTP-запросов необходимо настроить серверное окружение. Обычно для этого используется сервер, запущенный на определённом порту, с базой данных или мока для упрощённых тестов.

const app = require('../app');  // Импортируем приложение
const supertest = require('supertest');
const request = supertest(app);

describe('Интеграционные тесты API', function () {
  it('Тестирование GET-запроса', function (done) {
    request
      .get('/api/resource')
      .expect(200)
      .end(function (err, res) {
        if (err) return done(err);
        done();
      });
  });
});

В этом примере используется supertest для отправки GET-запроса на эндпоинт /api/resource. Метод .expect(200) проверяет, что ответ имеет статус 200 (OK).

Проверка содержимого ответа

Кроме проверки статусных кодов, важно проверять, соответствует ли содержимое ответа ожиданиям. Например, для JSON-ответов можно проверять структуру данных.

describe('Тестирование GET-запроса с JSON-ответом', function () {
  it('Получение списка пользователей', function (done) {
    request
      .get('/api/users')
      .expect(200)
      .expect('Content-Type', /json/)
      .end(function (err, res) {
        if (err) return done(err);
        
        // Проверка наличия ожидаемых данных в теле ответа
        const users = res.body;
        users.should.be.an('array');
        users[0].should.have.property('id');
        users[0].should.have.property('name');
        done();
      });
  });
});

В этом примере используется библиотека should.js для проверки типа данных в ответе и наличия обязательных полей в объекте. Метод .expect('Content-Type', /json/) гарантирует, что ответ будет в формате JSON.

Тестирование POST-запросов

Для POST-запросов важно проверять не только статусный код, но и правильность обработки данных, переданных в теле запроса. Пример:

describe('Тестирование POST-запроса', function () {
  it('Создание нового пользователя', function (done) {
    const user = { name: 'John Doe', email: 'john.doe@example.com' };
    
    request
      .post('/api/users')
      .send(user)
      .expect(201)
      .expect('Content-Type', /json/)
      .end(function (err, res) {
        if (err) return done(err);
        
        const createdUser = res.body;
        createdUser.should.have.property('id');
        createdUser.name.should.equal(user.name);
        createdUser.email.should.equal(user.email);
        done();
      });
  });
});

Здесь выполняется POST-запрос для создания нового пользователя. Метод .send(user) передаёт данные в теле запроса, а проверка на статус 201 подтверждает успешное создание ресурса.

Тестирование PUT и DELETE запросов

Для PUT и DELETE запросов аналогично проводятся тесты на проверку правильности обработки данных и изменений.

PUT-запрос
describe('Тестирование PUT-запроса', function () {
  it('Обновление данных пользователя', function (done) {
    const updatedUser = { name: 'Jane Doe', email: 'jane.doe@example.com' };
    
    request
      .put('/api/users/1')
      .send(updatedUser)
      .expect(200)
      .end(function (err, res) {
        if (err) return done(err);
        
        const user = res.body;
        user.should.have.property('id').equal(1);
        user.name.should.equal(updatedUser.name);
        user.email.should.equal(updatedUser.email);
        done();
      });
  });
});
DELETE-запрос
describe('Тестирование DELETE-запроса', function () {
  it('Удаление пользователя', function (done) {
    request
      .delete('/api/users/1')
      .expect(204)
      .end(function (err, res) {
        if (err) return done(err);
        done();
      });
  });
});

Для DELETE-запроса проверяется статус 204, который подтверждает успешное удаление ресурса.

Обработка ошибок

Важно тестировать также и обработку ошибок. Для этого можно создавать запросы, которые будут приводить к ошибке, например, с некорректными данными или неправильными путями.

describe('Тестирование ошибок', function () {
  it('Попытка получения несуществующего ресурса', function (done) {
    request
      .get('/api/nonexistent')
      .expect(404)
      .end(function (err, res) {
        if (err) return done(err);
        done();
      });
  });
});

Этот тест проверяет, что при обращении к несуществующему ресурсу сервер вернёт ошибку 404.

Взаимодействие с базой данных

Интеграционные тесты часто требуют взаимодействия с реальной базой данных. Это можно настроить так, чтобы тесты выполнялись в отдельной тестовой базе данных, а после каждого теста база очищалась.

Для создания и очистки базы данных можно использовать специальные библиотеки, такие как mongodb-memory-server для MongoDB.

Пример настройки с использованием базы данных:

const mongoose = require('mongoose');
const { MongoMemoryServer } = require('mongodb-memory-server');

let mongoServer;

before(async () => {
  mongoServer = await MongoMemoryServer.create();
  const uri = mongoServer.getUri();
  await mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true });
});

after(async () => {
  await mongoose.connection.dropDatabase();
  await mongoose.connection.close();
  await mongoServer.stop();
});

Здесь MongoDB запускается в памяти, и база данных очищается после выполнения тестов.

Асинхронные тесты и Promise

Большинство HTTP-запросов выполняются асинхронно, поэтому важно правильно работать с асинхронностью в тестах. Mocha поддерживает работу с асинхронными функциями с помощью done, Promise или async/await.

Пример с использованием async/await:

describe('Асинхронный тест', function () {
  it('Проверка асинхронного запроса', async function () {
    const res = await request.get('/api/resource');
    res.status.should.equal(200);
  });
});

Такой подход делает тесты более читаемыми и удобными для работы с асинхронными операциями.

Заключение

Интеграционные HTTP тесты играют ключевую роль в обеспечении качества веб-приложений. Использование Mocha в сочетании с такими инструментами, как supertest, позволяет эффективно тестировать API, проверяя как корректность ответов на запросы, так и взаимодействие с базой данных. Важно учитывать все возможные аспекты работы сервера, включая обработку ошибок, статусные коды и содержание ответа.