Этот пост в блоге на самом деле является черновиком стандартной операционной процедуры для моего бизнеса по разработке программного обеспечения и консультированию. За последние пару месяцев я три или четыре раза сталкивался с задачей написания тестов для HTTP API. Я пробовал несколько способов написания автоматизированных тестов, и это метод, к которому я пришел. TypeScript, Jest и супертест хорошо работают вместе, и их достаточно для реализации кратких тестов.

Предыстория и необходимость стандартизации

Несколько лет назад большая часть моего программирования для Интернета ограничивалась PHP. Тесты в этих проектах проводились с использованием PHPUnit и Guzzle. Позже мы также использовали Behat для более читаемых тестов. После того, как Docker стал популярным, монолит PHP превратился в приложение, созданное с использованием нескольких языков программирования. Некоторые небольшие сервисы теперь написаны на Node.js, а новый код для анализа данных в основном написан на Python.

Сначала казалось очевидным писать тесты уровня API на том же языке, что и сервер. Но после сравнения решений для Node.js, PHP и Python стало очевидно, что качество и подходы различаются между языками.

Помимо различий в качестве, проблема заключалась также в потере времени на изучение каждого тестового фреймворка. Потраченное время на изучение Python того стоило, потому что теперь мы можем использовать Pandas и Scipy для аналитики. И Jupyter отлично подходит для прототипирования. Напротив, обучение тестированию HTTP API на каждом языке не имеет значения. Также нет технических причин программировать веб-сервис и сопутствующие тесты на одном языке.

Наличие стандартной процедуры для такой задачи, как тесты HTTP API, дает множество преимуществ: вы пишете много тестов с одной и той же структурой. После того, как вы изучите основы, вы сможете сосредоточиться на проблемах более высокого уровня, таких как организация тестов, определение охвата и тестирование более сложных API. Каждый раз, когда вы создаете новый веб-сервис, вы не тратите время на гугление фраз вроде «какой лучший способ протестировать REST API?». Из-за решения стандартизировать эту часть моего рабочего процесса я сохраняю пропускную способность мозга для других решений.

Мой стандартный тестовый стек

Мой опыт работы с TypeScript за последние пару месяцев был исключительно положительным. Большую часть времени я использовал его во внешнем интерфейсе с Angular. Но он одинаково хорошо работает и на бэкэнде. Доступные инструменты и определения типов упрощают разработку с помощью Express. Функциональность автозаполнения в IDE, таких как VS Code, превосходна по сравнению с простым JavaScript. Проверка типов очень помогает, особенно при рефакторинге. Он поддерживает современные функции из ES2017, такие как async/await. Раньше я использовал Karma в качестве фреймворка для тестирования, но найти хорошее сочетание плагинов и написания конфигурации было кошмаром. Шутка — приятное улучшение. Он работает из коробки и имеет общие API, с которыми вы, возможно, уже знакомы: опиши, это, ожидай. Еще одним важным компонентом тестирования HTTP API является способ выполнения запросов. Суперагент имеет очень хорошую документацию и хорошо работает с TypeScript. Мне нравится его цепной синтаксис.

Пример тестирования простого GET-запроса

import request = require('supertest');
const BASE_URL = 'http://my-service:3000';
describe('Hello world', () => {
    it('Should say "hello world"', async() => {
        const response = await request(BASE_URL)
            .get('/')
            .expect(200);
        expect(response.text).toBe("hello world");
    });
});

Функция describe группирует тесты для определенной функции. Функция it определяет тест. В данном конкретном случае тест представляет собой асинхронную функцию. Это также может быть обычная функция или функция, возвращающая обещание. В следующих четырех строках мы видим элегантность синтаксиса async/await. Выполнение по-прежнему асинхронно, но синтаксис кажется очень линейным. Это согласовывает синтаксис с нашей ментальной моделью последовательных шагов. Строка .expect(200) утверждает, что код ответа действительно 200. И, наконец, мы также проверяем содержимое ответа.

POST-запросы

Отправка данных запросами POST и PUT выглядит так:

import request = require('supertest');
const BASE_URL = 'http://my-service:3000';
describe('POST /news', () => {
    it('Should save and return back the news', async() => {
        const response = await request(BASE_URL)
            .post('/news')
            .send({'title': 'Breaking news!'})
            .expect(201);
        expect(response.body).toEqual({'id': 1, 'title': 'Breaking news!'});
    });
});

Вместо get(url) мы использовали post(url) и добавили вызов send(data) для отправки нашего JSON.

Авторизация и другие заголовки

Заголовки можно установить с помощью set('header-name', 'value'), как показано в следующем примере:

import request = require('supertest');
const BASE_URL = 'http://my-service:3000';
describe('Authorization test', () => {
    it('Should respond with 401 if token invalid', async () => {
        await request(BASE_URL)
            .post('/admin/news')
            .set('Authorization', 'Bearer invalid')
            .send({})
            .expect(401)
            .expect('WWW-Authenticate', /^Bearer/);
    });
});

Я надеюсь, что эта статья поможет вам настроить тесты быстрее. Полный код доступен на https://gitlab.com/mdrolc/testing-http-apis. Там вы найдете простой сервис и соответствующие тесты.

Первоначально опубликовано на https://drola.si/post/2019-02-19-testing-http-apis 19 февраля 2019 г.