Как стать автором
Обновить
919.38
OTUS
Цифровые навыки от ведущих экспертов

Мокинг API на JavaScript с Pactum

Время на прочтение6 мин
Количество просмотров2.7K
Автор оригинала: Bas Dijkstra

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

У меня уже есть опыт работы с библиотеками имитации API, в первую очередь WireMock и WireMock.Net. В этой статье я постараюсь рассмотреть Pactum в сравнении с ними, рассмотрев некоторые важные функции, которые я ожидаю от любого инструмента для имитации API или библиотеки.

Начало работы: настройка и тестирование первого мока

Начнем с простейшего примера. Подобно WireMock и WireMock.Net, Pactum предлагает фейковый сервер, на который можно добавить имитацию ответов настоящего сервера. Как и у меня, у пользователей вышеупомянутых инструментов не должно возникнуть особых проблем во время начала работы с Pactum.

Запустить фейковый сервер на Pactum довольно просто:

const { mock, settings } = require('pactum');

beforeEach(async () => {

    settings.setLogLevel('ERROR');

    await mock.start(9876);
});

Я использовал конструкцию Jest beforeEach для запуска фейкового сервера перед каждым тестом. Затем я устанавливаю уровень логирования на ERROR, чтобы избавиться от некоторых записей на момент старта и завершения, которые Pactum записывает по умолчанию в консоль, поскольку мне это не нужно. Наконец, я запускаю фейковый сервер на порте 9876. Вот и все.

Выключить фейковый сервер после каждого теста так же просто:

afterEach(async () => {

    await mock.stop()
});

Если вы хотите запустить/остановить сервер только один раз, можно перед запуском теста заменить beforeEach и afterEach на beforeAll и afterAll соответственно. Я оставлю это на ваше усмотрение. Запуск и остановка сервера происходит очень, очень быстро, поэтому я не заметил снижения производительности, делая это подобным образом.

Теперь, когда мы можем запускать и останавливать сервер, давайте добавим ему первый мок-ответ:

function addHelloWorldResponse() {

    mock.addInteraction({
        request: {
            method: 'GET',
            path: '/api/hello-world'
        },
        response: {
            status: 200,
            body: 'Hello, world!'
        }
    });
}

Мок-ответы добавляются на фейковый сервер Pactum посредством так называемых взаимодействий. Взаимодействие содержит информацию о запросе, на который нужно получить ответ с использованием сопоставления запросов (подробнее об этом позже), а также мок-ответ, который должен вернуться. В этом случае мы хотим ответить на HTTP-запрос GET к /api/hello-world ответом с кодом состояния HTTP 200 и текстовым телом ответа Hello, world!.

Чтобы проверить, работает ли это, мы напишем тест с помощью Pactum, который делает запрос к тестовому эндпоинту на нашем локальном хосте через порт 9876:

const pactum = require('pactum');

describe('Demonstrating that Pactum API mocking can', () => {

    test('return a basic REST response', async () => {

        addHelloWorldResponse();

        await pactum.spec()
            .get('http://localhost:9876/api/hello-world')
            .expectStatus(200)
            .expectBody('Hello, world!')
    });
});

Запуск этого теста приводит к следующему результату, откуда мы видим, что мок ведет себя так, как мы и ожидали:

Running a test and seeing that the mock behaves as expected
Running a test and seeing that the mock behaves as expected

Сопоставление запросов

В предыдущем примере сопоставление запросов (то есть просмотр конкретных характеристик входящего запроса для определения соответствующего ответа) выполнялось путем просмотра HTTP-операции (GET) и эндпоинта (/api/hello-world). Pactum также предлагает пару других стратегий сопоставления запросов, включая заголовки запросов и их значения (что полезно для аутентификации), параметры запроса и их значения, а также содержимое тела запроса.

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

function addQueryParameterRequestMatchingResponses() {

    mock.addInteraction({
        request: {
            method: 'GET',
            path: '/api/zip',
            queryParams: {
                zipcode: 90210
            }
        },
        response: {
            status: 200,
            body: {
                zipcode: 90210,
                city: 'Beverly Hills'
            }
        }
    });

    mock.addInteraction({
        request: {
            method: 'GET',
            path: '/api/zip',
            queryParams: {
                zipcode: 12345
            }
        },
        response: {
            status: 200,
            body: {
                zipcode: 12345,
                city: 'Schenectady'
            }
        }
    });
}

Это даст указание фейковому серверу Pactum отвечать на:

  • HTTP-запрос GET на /api/zip?zipcode=90210 с телом ответа {zipcode: 90210, city: 'Beverly Hills'}

  • HTTP-запрос GET на /api/zip?zipcode=12345 с телом ответа {zipcode: 12345, city: 'Schenectady'}

  • все другие запросы (включая запросы к /api/zip с другими значениями параметра запроса zipcode) с кодом ответа HTTP 404 (ответ по умолчанию для несовпадающего запроса).

Данный репозиторий GitHub содержит тесты, показывающие, что описанный выше мок ведет себя так, как и ожидалось.

Имитация сред с различной производительностью

Еще одна полезная функция любой библиотеки имитации API — возможность определять производительность или того, сколько фейковый сервер должен ждать, прежде чем ответить на запрос. Например, код ниже определяет мок, который возвращает ответ после ожидания фиксированной задержки в 1000 миллисекунд:

function addDelayedResponse() {

    mock.addInteraction({
        request: {
            method: 'GET',
            path: '/api/delay'
        },
        response: {
            status: 200,
            fixedDelay: 1000
        }
    })
}

Чтобы быть ближе к реальности, Pactum также позволяет вам рандомизировать задержку и указать минимальные и максимальные ее значения.

Этот тест, вызывающий мок-ответ с задержкой, завершается ошибкой:

test('return a REST response with a delay', async () => {

    addDelayedResponse();

    await pactum.spec()
        .get('http://localhost:9876/api/delay')
        .expectStatus(200)
        .expectResponseTime(1000)
});

Pactum позволяет установить только верхний предел ожидаемого времени отклика. Поскольку фактическое время отклика составляет более 1000 миллисекунд (мы добавили задержку плюс некоторое время обработки), тест завершается ошибкой, демонстрируя тем самым, что задержка была применена успешно. Если бы это было не так, тест был бы пройден, потому что отправка запроса и обработка ответа обычно занимает всего пару миллисекунд.

Повторное использование значений из запроса

Часто при создании имитации ответа API необходимо повторно использовать значения из запроса (уникальные идентификаторы, файлы cookie, другие динамические значения). С Pactum можно сделать и это:

const { like } = require('pactum-matchers');

function addReusePathParameterValueResponse() {

    mock.addInteraction({
        request: {
            method: 'GET',
            path: '/api/user/{id}',
            pathParams: {
                id: like('random-id')
            }
        },
        stores: {
            userId: 'req.pathParams.id'
        },
        response: {
            status: 200,
            body: {
                message: `Returning data for user $S{userId}`
            }
        }
    });
}

Это определение мока идентифицирует определенную часть пути как параметр пути id, ссылаясь здесь на ID пользователя, и сохраняет его для повторного использования под именем userId. Затем его можно повторно использовать при построении ответа, что мы и делаем здесь, используя его в шаблонной строке, ссылаясь на ранее сохраненное значение с помощью $S{userId}. Обратите внимание на букву S, которая, я предполагаю, относится к чему-то вроде «хранилища», где Pactum хранит значения.

Этот управляемый данными тест (см предыдущую статью) показывает, что фиктивный сервер Pactum успешно извлекает значение параметра path из запроса и повторно использует его в теле ответа:

test.each(
[[1], [2], [3]]
)('use response templating to return the expected message for user %i', async (userId) => {

    addReusePathParameterValueResponse();

    await pactum.spec()
        .get('http://localhost:9876/api/user/{user}')
        .withPathParams('user', userId)
        .expectStatus(200)
        .expectJsonMatch('message', `Returning data for user ${userId}`)
});

Точно так же Pactum может извлекать значения параметров query, значения заголовков, а также значения тела запроса для повторного использования в ответе.

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

В этой статье я не исследовал возможность симулировать стейтфул поведение, то есть моделирование «состояния» или «памяти» в моке API. Там, где WireMock делает это с помощью конечных автоматов, с Pactum вы, вероятно, можете сделать что-то подобное, используя конструкцию onCall.

Это не то же самое, что подход FSM в WireMock (и WireMock.Net), но для простых сценариев вы сможете получить аналогичные результаты.

Весь код из этой статьи можно найти на GitHub. В следующей статье я исследую возможности Pactum по тестированию контрактов.


Когда ошибку находит тестировщик, то он создает задачу, в которой подробно описывает проблему. Когда ошибку находит автотест, то ... Кстати, а что происходит, когда автотест находит ошибку? Если у вас нет ответа на этот вопрос, то приходите на открытое занятие «Отчет для автотестов». Все подробно расскажем и покажем на примерах. Регистрация на для всех желающих по ссылке.

Теги:
Хабы:
Всего голосов 6: ↑5 и ↓1+4
Комментарии3

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS