Тестирование API-вызовов

Тестирование API-вызовов — проверка кода, который делает HTTP-запросы, через мокирование fetch/axios (unit) или реальные запросы к тест-серверу (integration).

Зачем нужно

Реальные HTTP-запросы в unit-тестах делают их медленными, нестабильными и зависимыми от внешних сервисов. Мокирование позволяет проверить все сценарии: успех, ошибка сети, 404, 500 — предсказуемо и быстро. Integration-тест с реальным сервером проверяет корректность взаимодействия.

Где используется

  • Unit-тесты сервисов, работающих с API
  • Тестирование React компонентов с data-fetching
  • Integration-тесты Node.js API с supertest
  • Тестирование retry-логики и обработки ошибок

Основной контент

Мокирование fetch через jest.mock

// api.js
export async function getUser(id) {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
  return res.json();
}

// api.test.js
global.fetch = jest.fn;

afterEach(() => {
  jest.clearAllMocks;
});

test('возвращает данные пользователя', async () => {
  fetch.mockResolvedValue({
    ok: true,
    json: async  => ({ id: 1, name: 'Alice' }),
  });

  const user = await getUser(1);

  expect(fetch).toHaveBeenCalledWith('/api/users/1');
  expect(user.name).toBe('Alice');
});

test('выбрасывает ошибку при 404', async () => {
  fetch.mockResolvedValue({ ok: false, status: 404 });

  await expect(getUser(999)).rejects.toThrow('HTTP error: 404');
});

Мокирование axios

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

jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

test('получает пользователя', async () => {
  mockedAxios.get.mockResolvedValue({ data: { id: 1, name: 'Alice' } });

  const user = await getUser(1);

  expect(mockedAxios.get).toHaveBeenCalledWith('/api/users/1');
  expect(user.name).toBe('Alice');
});

test('выбрасывает ошибку при сетевой ошибке', async () => {
  mockedAxios.get.mockRejectedValue(new Error('Network Error'));

  await expect(getUser(1)).rejects.toThrow('Network Error');
});

MSW (Mock Service Worker) — перехват на уровне сети

npm install --save-dev msw
// mocks/handlers.ts
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('/api/users/:id', ({ params }) => {
    return HttpResponse.json({ id: Number(params.id), name: 'Alice' });
  }),

  http.post('/api/users', async ({ request }) => {
    const body = await request.json();
    return HttpResponse.json({ id: 1, ...body }, { status: 201 });
  }),

  http.get('/api/users/999', () => {
    return HttpResponse.json({ error: 'Not found' }, { status: 404 });
  }),
];

// mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);

// jest.setup.ts
import { server } from './mocks/server';
beforeAll( => server.listen);
afterEach( => server.resetHandlers);
afterAll( => server.close());

Integration-тест Node.js API с supertest

// users.router.test.ts
import request from 'supertest';
import { app } from '../app';
import { db } from '../db';

beforeEach(async () => { await db('users').truncate; });
afterAll(async () => { await db.destroy(); });

test('GET /users возвращает список', async () => {
  await db('users').insert([{ name: 'Alice' }, { name: 'Bob' }]);

  const res = await request(app).get('/users').expect(200);

  expect(res.body).toHaveLength(2);
  expect(res.body[0].name).toBe('Alice');
});

Частые ошибки

  • Реальные HTTP в unit-тестах — зависимость от сети делает тесты нестабильными; всегда мокируй fetch/axios в unit-тестах
  • Нет теста на сетевую ошибку — проверяй не только успешный ответ, но и Network Error, таймаут, 5xx
  • Mock не соответствует реальному ответу — если структура mock-данных отличается от реального API, тест бесполезен; синхронизируй с типами

Связанные темы

Ресурсы