nock для TypeScript: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

nock для TypeScript: HTTP-мокирование в тестах

1. Что такое nock и зачем она нужна?

nock — это мощная библиотека для Node.js, предназначенная для перехвата и мокирования HTTP-запросов. Она позволяет разработчикам имитировать ответы от внешних сервисов (REST API, сторонние микросервисы, вебхуки) без необходимости реального выполнения сетевых запросов. Это особенно важно при написании юнит-тестов и интеграционных тестов, где стабильность и скорость выполнения имеют критическое значение.

Зачем это нужно? Представьте, что ваше приложение отправляет запросы к платежному шлюзу, погодному API или базе данных через HTTP. В тестовой среде вы не хотите зависеть от доступности этих сервисов, их скорости ответа или лимитов запросов. nock позволяет "подменить" эти запросы, возвращая заранее заданные данные. Это делает тесты быстрыми, предсказуемыми и изолированными от внешних факторов. Кроме того, вы можете тестировать сценарии ошибок (таймауты, коды 500, пустые ответы), которые сложно воспроизвести в реальных условиях.

В контексте TypeScript nock отлично интегрируется с популярными тестовыми фреймворками (Jest, Mocha, Vitest) и поддерживает типизацию, что делает работу с ней удобной и безопасной. Библиотека перехватывает запросы на уровне Node.js (через модуль http и https), поэтому она работает с любыми HTTP-клиентами: axios, node-fetch, got и даже нативным fetch (с некоторыми оговорками).

2. Установка

Для установки nock в проект на TypeScript выполните одну из следующих команд в терминале:

# Через npm
npm install --save-dev nock @types/nock

# Через yarn
yarn add --dev nock @types/nock

# Через pnpm
pnpm add --save-dev nock @types/nock

Важно: @types/nock содержит декларации типов для TypeScript. Если вы используете TypeScript 4.0+, типы могут быть уже встроены в саму библиотеку, но для совместимости лучше установить их явно.

3. Быстрый старт

Давайте напишем минимальный тест, который демонстрирует базовую работу nock. Предположим, у нас есть функция, которая получает данные пользователя с внешнего API.

// userService.ts
import axios from 'axios';

export async function getUser(userId: number): Promise<{ id: number; name: string; email: string }> {
    const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`);
    return response.data;
}

Теперь напишем тест, который мокирует этот запрос:

// userService.test.ts
import nock from 'nock';
import { getUser } from './userService';

describe('getUser', () => {
    afterEach(() => {
        nock.cleanAll(); // Очищаем все моки после каждого теста
    });

    it('должен вернуть данные пользователя', async () => {
        // 1. Настраиваем перехват
        const mockUser = { id: 1, name: 'John Doe', email: 'john@example.com' };
        nock('https://jsonplaceholder.typicode.com')
            .get('/users/1')
            .reply(200, mockUser);

        // 2. Вызываем тестируемую функцию
        const user = await getUser(1);

        // 3. Проверяем результат
        expect(user).toEqual(mockUser);
    });

    it('должен выбросить ошибку при 500', async () => {
        nock('https://jsonplaceholder.typicode.com')
            .get('/users/1')
            .reply(500, { error: 'Server Error' });

        await expect(getUser(1)).rejects.toThrow();
    });
});

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

4. Основные методы и функции

Рассмотрим ключевые методы nock. Каждый метод будет представлен с сигнатурой, описанием и примером.

4.1. nock(baseUrl: string, options?: NockOptions)

Сигнатура: nock(baseUrl: string, options?: { allowUnmocked?: boolean; reqheaders?: Record<string, string>; ... }): Scope

Что делает: Создает новый перехватчик (scope) для указанного базового URL. Все последующие методы (get, post, reply) будут применяться к этому scope. Это отправная точка для любого мокирования.

import nock from 'nock';

// Создаем перехватчик для API example.com
const scope = nock('https://api.example.com');
// Теперь можно добавлять правила для конкретных эндпоинтов
scope.get('/users').reply(200, []);

4.2. scope.get(path: string, requestBody?: RequestBodyMatcher, options?: RequestOptions)

Сигнатура: .get(path: string, requestBody?: string | RegExp | object | Function, options?: { ... }): Interceptor

Что делает: Перехватывает HTTP GET-запросы по указанному пути. Можно дополнительно указать тело запроса (для случаев, когда GET-запрос содержит тело, что редко, но возможно) или query-параметры.

nock('https://api.github.com')
    .get('/repos/nodejs/node')
    .reply(200, { name: 'node', stars: 100000 });

// Перехват с query-параметрами
nock('https://api.example.com')
    .get('/search')
    .query({ q: 'typescript', page: 1 })
    .reply(200, { results: [] });

4.3. scope.post(path: string, requestBody?: RequestBodyMatcher, options?: RequestOptions)

Сигнатура: .post(path: string, requestBody?: string | RegExp | object | Function, options?: { ... }): Interceptor

Что делает: Перехватывает HTTP POST-запросы. Часто используется для мокирования создания ресурсов. Можно задать точное тело запроса, регулярное выражение или функцию для валидации.

nock('https://api.example.com')
    .post('/users', { name: 'Alice', role: 'admin' })
    .reply(201, { id: 123, name: 'Alice' });

// Использование функции для валидации тела
nock('https://api.example.com')
    .post('/login', (body) => body.username === 'admin' && body.password.length > 5)
    .reply(200, { token: 'abc123' });

4.4. interceptor.reply(statusCode: number, body?: Body, headers?: Headers)

Сигнатура: .reply(statusCode: number, body?: string | Buffer | object, headers?: Record<string, string>): Scope

Что делает: Определяет ответ, который будет возвращен на перехваченный запрос. Принимает HTTP-статус код, тело ответа и опциональные заголовки. Это основной способ задания ответа.

nock('https://api.example.com')
    .get('/status')
    .reply(200, { status: 'ok' }, { 'X-Custom-Header': 'value' });

// Ответ с задержкой (симуляция сети)
nock('https://api.example.com')
    .get('/slow')
    .delay(2000) // задержка 2 секунды
    .reply(200, { data: 'slow response' });

4.5. interceptor.replyWithError(error: Error | string)

Сигнатура: .replyWithError(error: Error | string): Scope

Что делает: Симулирует ошибку на уровне сети (например, разрыв соединения, таймаут DNS). Полезно для тестирования обработки исключений, когда сервер недоступен.

nock('https://api.example.com')
    .get('/unstable')
    .replyWithError('Connection refused');

// Или с объектом Error
nock('https://api.example.com')
    .get('/broken')
    .replyWithError(new Error('Network timeout'));

4.6. scope.persist()

Сигнатура: .persist(flag?: boolean): Scope

Что делает: Делает перехватчик постоянным. По умолчанию nock перехватывает запрос только один раз, после чего "снимает" мок. Вызов .persist() позволяет использовать один и тот же мок многократно. Это удобно для мокирования часто вызываемых эндпоинтов.

// Мок будет работать для всех последующих GET запросов к /health
nock('https://api.example.com')
    .get('/health')
    .reply(200, { status: 'healthy' })
    .persist();

// Теперь можно вызывать сколько угодно раз
await fetch('https://api.example.com/health'); // OK
await fetch('https://api.example.com/health'); // OK снова

4.7. scope.log(enableLogging: boolean)

Сигнатура: .log(enableLogging: boolean): Scope

Что делает: Включает или выключает логирование для данного scope. Когда логирование включено, nock выводит в консоль информацию о перехваченных запросах и ответах. Это очень полезно для отладки, когда вы не уверены, что ваш мок срабатывает.

nock('https://api.example.com')
    .log(true) // Включаем логирование
    .get('/debug')
    .reply(200, { message: 'ok' });

// В консоли вы увидите:
// nock: intercept GET https://api.example.com/debug
// nock: matching GET https://api.example.com/debug
// nock: reply 200

4.8. nock.cleanAll()

Сигнатура: nock.cleanAll(): void

Что делает: Глобальная функция, которая удаляет все активные перехватчики и восстанавливает стандартное поведение HTTP. Обязательно вызывайте этот метод в хуке afterEach или afterAll вашего тестового фреймворка, чтобы избежать "загрязнения" тестов.

import nock from 'nock';

beforeEach(() => {
    nock.cleanAll(); // Гарантируем чистоту перед каждым тестом
});

afterAll(() => {
    nock.cleanAll(); // Финальная очистка
});

4.9. nock.restore()

Сигнатура: nock.restore(): void

Что делает: Полностью отключает перехват HTTP-запросов, восстанавливая оригинальные модули http и https. В отличие от cleanAll, который удаляет только правила, restore выключает сам механизм перехвата. Используйте это, если хотите временно отключить nock в середине теста.

nock('https://api.example.com')
    .get('/data')
    .reply(200, { mocked: true });

// Выключаем перехват
nock.restore();

// Этот запрос пойдет в реальный интернет
const response = await fetch('https://api.example.com/data');

// Включаем обратно
nock.activate(); // Включает перехват снова

4.10. scope.intercept(path: string, method: string, requestBody?: RequestBodyMatcher, options?: RequestOptions)

Сигнатура: .intercept(path: string, method: string, requestBody?: string | RegExp | object | Function, options?: { ... }): Interceptor

Что делает: Универсальный метод для перехвата запросов с любым HTTP-методом (GET, POST, PUT, DELETE, PATCH и т.д.). Полезен, когда метод определяется динамически.

nock('https://api.example.com')
    .intercept('/items', 'DELETE')
    .reply(204);

// Эквивалентно:
nock('https://api.example.com')
    .delete('/items')
    .reply(204);

4.11. interceptor.times(number: number)

Сигнатура: .times(number: number): Interceptor

Что делает: Ограничивает количество раз, которое данный перехватчик может быть использован. По умолчанию — 1 раз (если не используется persist). Позволяет точно контролировать, сколько запросов будет перехвачено.

nock('https://api.example.com')
    .get('/retry')
    .times(3) // Перехватит первые 3 запроса
    .reply(500, { error: 'Retry later' });

// Четвертый запрос пойдет в реальный сервер (или вызовет ошибку, если не настроено)

4.12. interceptor.delay(delayInMs: number)

Сигнатура: .delay(delayInMs: number): Interceptor

Что делает: Добавляет задержку перед отправкой ответа. Полезно для тестирования таймаутов и асинхронного поведения

Рекомендаций