Потоки API или интеграция API означает выполнение последовательности запросов API, где каждый запрос зависит от вывода предыдущих запросов.

Самый простой пример:
1. Выполните запрос для аутентификации и получите барьер токена
2. Используйте токен для выполнения другого запроса API.

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

Хотя можно использовать такие инструменты, как почтальон, для написания и тестирования запросов к вашему серверу, я считаю, что намного проще, тем не менее, если вы программист, писать свои тесты в коде javascript, используя такие библиотеки, как Mocha и Чай.
Написание кода для тестирования вашего API дает вам гораздо больше гибкости, повторного использования и контроля версий.

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

Сначала создайте папку с именем `github-api-testing`, откройте эту папку в окне терминала и запустите 'npm init -y'.
Это приведет к создайте 'package.json', который помогает npm управлять всеми внешними библиотеками и зависимостями, которые нам понадобятся.

Инструменты

Запустите «npm install --save mocha chai supertest supertest-as-as-as-as-as-которые».
Это установит несколько внешних пакетов, необходимых для запуска тестов. и зарегистрировали их как зависимости в нашем файле package.json.

Я объясню немного по каждому из этих пакетов:

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

Chai - это библиотека утверждений, она позволяет нам утверждать, что наш код ведет себя так, как ожидалось, сохраняя при этом его читабельность и простоту для понимания.

Супертест - это библиотека утверждений по HTTP-запросам. Он выполняет HTTP-запрос к API и позволяет устанавливать ожидания, такие как проверка заголовков, статуса и тела ответа.
Другие библиотеки, такие как hippie, api-easy, chai-http и frisby предоставляют аналогичную функциональность.

Supertest-as-обещанный - это оболочка вокруг Supertest, которая предоставляет объекту запроса интерфейс Promise, поэтому мы можем использовать такие методы, как .then () и .catch () в наших тестах вместо передачи функций обратного вызова.
Это имеет большее значение при выполнении потокового тестирования, поскольку позволяет вам определять последовательность в более удобочитаемом синтаксисе.

Написание тестов

В целях демонстрации мы протестируем общедоступный API github и проверим следующий поток:

1. Используйте API '/ users? Since = {userId}', чтобы получить имя конкретного пользователя
2. Используйте API '/ users / {username} / repos' для получить список репозиториев этого пользователя
3. Вызовите '/ repos / {username} / {repository} / members' API, чтобы запросить список участников репозитория, принадлежащего этому пользователю.

Теперь, когда библиотеки готовы, давайте создадим папку «integration-tests», в которую мы поместим наши тестовые файлы.

Создайте файл с именем ‘get-user-repositories.test.js’ и добавьте следующий код:

const request = require("supertest-as-promised");
const chai = require(‘chai’);
const { assert, expect } = chai;
chai.should();

Этот фрагмент импортирует библиотеки, которые мы установили ранее, и позволяет нам использовать их внутри нашего кода.

Теперь мы определим базовый объект supertest‘ request ’ и функции, которые будут использовать его для вызова запросов API:

const baseRequest = request('https://api.github.com'); 
function getUsersSinceId(userId) {  
  return baseRequest
    .get(`/users?since=${userId}`)
    .set('Accept', 'application/json')
    .expect('Content-Type', /json/)
    .expect(200)
    .then(function(result) {
      const users = result.body;
      expect(users).to.be.an('Array', "Couldn't get users");
      return users;
    });
} 
const getFirst = items => items[0];
function getRepositoriesForUser(username) {
  return baseRequest
    .get(`/users/${username}/repos`)
    .set('Accept', 'application/json')
    .expect('Content-Type', /json/)
    .expect(200)
    .then(function(result) {
      const repos = result.body;
      expect(repos).to.be.an('Array', "Couldn't get repositories");
      return repos;
    });
} 
function getContributorsForRepository(username, repository) {
  return baseRequest
    .get(`/repos/${username}/${repository}/contributors`)
    .set('Accept', 'application/json')
    .expect('Content-Type', /json/)
    .expect(200)
    .then(function(result) {
      const contributors = result.body;
      expect(contributors).to.be.an('Array', "Couldn't get contributors");
      return contributors;
    });
}

Каждая из вышеперечисленных функций выполняет сетевой запрос к API и подтверждает определенные ожидания, такие как заголовки, код состояния и возвращаемая структура данных.
Мы будем использовать эти функции в качестве блоков lego для наших потоковых тестов, комбинируя их в определенном порядке.

Давайте добавим код для проверки желаемого потока:

describe("Verify user\'s repository has any contributor", function() {
  this.timeout(5000);
  this.slow(1000);
  const state = {};
  state.passed = true;
  
  afterEach(function() {
    state.passed = state.passed &&
    (this.currentTest.state === "passed");
  });
 
  beforeEach(function() {
    if (!state.passed) {
      return this.currentTest.skip();
    }
  });
 
  it('should get username for user-id 7034093', function() {
    return getUsersSinceId(7034093)
      .then(getFirst)
      .then((user) => state.username = user.login);
  });   
  it('should get first repository name', function() {
    return getRepositoriesForUser(state.username)
      .then(repos => state.repos = repos)
      .then(getFirst)
      .then(repo => state.repo = repo.name);
  });
  it('should get repository contributors', function() {
    return getContributorsForRepository(state.username, state.repo)
      .then(contributors => contributors.should.have.length.above(0));
  });
});

Функция «описать» объединяет несколько тестов.

Мы используем this.timeout () и this.slow (), чтобы указать продолжительность (в миллисекундах), в течение которой тест будет считаться медленным или истекшим по времени.
Медленные тесты будут отмечены желтым в репортере mocha 'spec'.
Истекшие по времени тесты будут считаться неудачными.

Затем мы создаем объект state, который будет использоваться для передачи необходимых данных из одного теста в другой.

Мы определяем свойство «пройдено» для захвата состояния последнего теста и используем функцию «afterEach» для обновления этого состояния после каждого запущенного теста.
Мы затем можно использовать функцию «beforeEach», чтобы пропустить все оставшиеся тесты в случае неудачи одного из тестов.

Теперь мы можем определять тесты с помощью функций «it», каждая такая функция имеет заголовок и тело.
Mocha будет запускать тесты в том порядке, в котором они определены, сообщая об успешном / неудачном статусе для каждое название теста.

Мы выполняем вызовы API из наших тестовых функций, используя «блоки лего», которые мы создали ранее.
И мы сохраняем возвращаемое значение от каждого вызова в объекте state, поэтому следующие тесты могут использовать эти данные.

Чтобы запустить тесты, запустите 'mocha tests / ** / *. Js'.
Мы также можем определить его как скрипт npm, отредактировав файл package.json раздел файловых скриптов:

“scripts”: {
  “test”: “mocha tests/**/*.js”
},

Теперь вы можете просто запустить ‘npm test’, чтобы выполнить тесты.

Результат будет примерно таким:

Условные обозначения

Обычно при написании модульных тестов вы хотите, чтобы тесты были как можно более отделены друг от друга.

Я мог бы написать потоковый тест таким же образом, исключив необходимость в объекте 'state' и 'beforeEach' / 'afterEach 'и иметь весь код в одной тестовой функции:

it('should verify user\'s repository has any contributor', function () {
    return getUsersSinceId(7034093)
      .then(getFirst)
      .then((user) => user.login)
      .then((username) => {
        return getRepositoriesForUser(username)
          .then(getFirst)
          .then(repo => repo.name)
          .then((repoName) => {
            return getContributorsForRepository(username, repoName)
            .then(contributors => contributors.should.have.length.above(0));
          });
        });
  });

Основная причина, по которой я решил разделить каждый шаг в потоке на отдельную функцию тестирования ('it'), заключается в том, что я чувствую, что это дает мне наиболее читаемый отчет и помогает определить точную точку, в которой поток потерпел неудачу.
Трассы стека и сообщения об ошибках, которые вы получаете от mocha и supertest при работе с обещаниями, недостаточно ясны, поэтому вы должны иметь отчет об испытаниях должен быть максимально информативным и позволять вам точно видеть, где именно произошел разрыв потока.

Еще одна вещь, о которой стоит упомянуть, и которая отличается от того, как я писал бы модульные тесты, заключается в том, что некоторые утверждения были написаны внутри функций «блока лего», которые фактически выполняют HTTP-запросы, а некоторые из утверждений были сделаны внутри фактического 'it' function .
При таком разделении кода я думал, что при интеграционном / потоковом тестировании у меня могут быть одни и те же блоки функциональности, повторяющиеся в несколько разный расход. Например, я мог бы начать весь свой поток с запроса «входа в систему».
Поэтому я попытался построить эти низкоуровневые блоки как можно более общими и включить только логику утверждения, связанную с сетью и структурой ответа. .
Фактический поток и утверждение, относящиеся к бизнес-логике, будут выполняться внутри самой функции тестирования (например, утверждение количества участников в нашем примере).

Мы даже можем пойти дальше и структурировать наши файлы таким образом, извлекая низкоуровневые блоки в отдельный файл (например, `api-units.js`) и заставляя тесты импортировать оттуда необходимые функции.

integration-tests/
├── tests
│ ├── api-units.js
│ ├── get-user-repositories.test.js
└── package.json

Заключение

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

Полный код этого сообщения можно найти по адресу https://github.com/avivr/github-api-tests.

Если у вас есть какие-либо вопросы, комментарии, идеи по улучшению или вы хотите поделиться тем, как вы проводите тестирование API - не стесняйтесь обращаться.