Гид по технологиям

Тестирование Express REST API с Jest и SuperTest

5 min read Backend Обновлено 21 Nov 2025
Тестирование Express REST API с Jest и SuperTest
Тестирование Express REST API с Jest и SuperTest

галочка на жёлтом стикере, символ успешной проверки

Что вы узнаете (основная цель)

  • Как настроить Jest и SuperTest для тестирования HTTP-эндпойнтов.
  • Примеры тестов для GET /todos, POST /todo, PUT /todos/:id и DELETE /todos/:id.
  • Практические рекомендации: подготовка окружения, фикстуры, изоляция тестов и обработка ошибок.

Что такое Jest?

Jest — это фреймворк для тестирования на JavaScript, изначально разработанный для проектов на React. Он включает раннер тестов и собственную систему утверждений (assertions). В контексте Node.js Jest хорошо подходит для unit- и интеграционных тестов благодаря простоте конфигурации и поддержке асинхронных тестов.

Определение: unit-тест — тест, проверяющий одну небольшую единицу кода. Интеграционный тест — проверяет взаимодействие нескольких компонентов (например, HTTP-сервер + обработчики).

Что такое SuperTest?

SuperTest — библиотека для тестирования HTTP в Node.js. На базе superagent, она предоставляет объект request, которому можно передать базовый URL и затем вызывать .get(), .post(), .put(), .delete() и другие методы для отправки HTTP-запросов. SuperTest удобен для интеграционных тестов API, когда необходимо симулировать реальное HTTP-взаимодействие.

Пример использования SuperTest (сокращённо):

const request = require("supertest")
request("https://icanhazdadjoke.com")
  .get('/slack')
  .end(function(err, res) {
    if (err) throw err;
    console.log(res.body.attachments);
  });

Здесь вы передаёте базовый URL в request, затем вызываете метод и обрабатываете ответ в .end(). В Jest обычно используют async/await и методы библиотеки, чтобы сравнивать ожидаемые и фактические значения.

Создание простого Express API

Чтобы тестировать эндпойнты, сначала создайте минимальный REST API. Мы реализуем CRUD над массивом todos в памяти.

  1. Создайте директорию и инициализируйте npm:
mkdir node-jest
npm init -y
  1. Установите зависимости (включая express) и инструменты для тестов:
npm install express
npm install --save-dev jest supertest
  1. Создайте файл index.js с базовым сервером. Обратите внимание: для чтения body в Express используйте express.json().
const express = require("express")
const app = express()
app.use(express.json())

const todos = []

app.listen(3000, () => console.log("Listening at port 3000"))

// Get all todos
app.get("/todos", (req, res) => {
  return res.status(200).json({
    data: todos,
    error: null,
  });
});

// Create todo
app.post("/todo", (req, res) => {
  try {
    const { id, item, completed } = req.body;
    const newTodo = { id, item, completed };
    todos.push(newTodo);
    return res.status(201).json({ data: todos, error: null });
  } catch (error) {
    return res.status(500).json({ data: null, error: error });
  }
});

// Update todo
app.put("/todos/:id", (req, res) => {
  try {
    const id = req.params.id;
    const todo = todos.find((t) => t.id == id);
    if (!todo) {
      throw new Error("Todo not found");
    }
    todo.completed = req.body.completed;
    return res.status(201).json({ data: todo, error: null });
  } catch (error) {
    return res.status(500).json({ data: null, error: error });
  }
});

// Delete todo
app.delete("/todos/:id", (req, res) => {
  try {
    const id = req.params.id;
    const index = todos.findIndex((t) => t.id == id);
    if (index !== -1) {
      todos.splice(index, 1);
    }
    return res.status(200).json({ data: todos, error: null });
  } catch (error) {
    return res.status(500).json({ data: null, error: error });
  }
});

module.exports = app; // экспорт для тестов (если запускать через supertest напрямую)

Важно: в продакшн-коде вместо in-memory массива используйте базу данных и мокайте/фейковайте её в тестах.

Настройка Jest

Добавьте в package.json скрипт тестов:

{
  "scripts": {
    "test": "jest"
  }
}

Jest по умолчанию найдёт файлы с суффиксом .test.js или .spec.js.

Тестирование GET /todos

Создайте файл api.test.js в корне проекта. Для запусков HTTP-запросов используем SuperTest.

const request = require("supertest")
const baseURL = "http://localhost:3000"
const crypto = require('crypto')

describe("GET /todos", () => {
  const newTodo = {
    id: crypto.randomUUID(),
    item: "Drink water",
    completed: false,
  }

  beforeAll(async () => {
    // создаём тестовые данные
    await request(baseURL).post("/todo").send(newTodo);
  })

  afterAll(async () => {
    await request(baseURL).delete(`/todos/${newTodo.id}`)
  })

  it("should return 200", async () => {
    const response = await request(baseURL).get("/todos");
    expect(response.statusCode).toBe(200);
    expect(response.body.error).toBe(null);
  });

  it("should return todos", async () => {
    const response = await request(baseURL).get("/todos");
    expect(response.body.data.length >= 1).toBe(true);
  });
});

Комментарий: beforeAll используется для подготовки тестовых данных, afterAll — для очистки. В боевых условиях лучше использовать тестовую БД и сбрасывать её после тестов.

Тестирование POST /todo

POST требует отправки JSON-тела. Пример теста:

describe("POST /todo", () => {
  const newTodo = {
    id: crypto.randomUUID(),
    item: "Write tests",
    completed: false,
  }

  afterAll(async () => {
    await request(baseURL).delete(`/todos/${newTodo.id}`)
  })

  it("should add an item to todos array", async () => {
    const response = await request(baseURL).post("/todo").send(newTodo);
    const lastItem = response.body.data[response.body.data.length - 1];
    expect(response.statusCode).toBe(201);
    expect(lastItem.item).toBe(newTodo.item);
    expect(lastItem.completed).toBe(newTodo.completed);
  });
});

Совет: для надёжности проверяйте не только статус, но и структуру ответа (schema), например с помощью jest-extended или AJV.

Тестирование PUT /todos/:id

Пример обновления:

describe("Update one todo", () => {
  const newTodo = {
    id: crypto.randomUUID(),
    item: "Temporary todo",
    completed: false,
  }

  beforeAll(async () => {
    await request(baseURL).post("/todo").send(newTodo);
  })

  afterAll(async () => {
    await request(baseURL).delete(`/todos/${newTodo.id}`)
  })

  it("should update item if it exists", async () => {
    const response = await request(baseURL)
      .put(`/todos/${newTodo.id}`)
      .send({ completed: true });
    expect(response.statusCode).toBe(201);
    expect(response.body.data.completed).toBe(true);
  });
});

Проверьте также поведение при отсутствии элемента (404 или 500 в примере с throw — лучше вернуть 404).

Тестирование DELETE /todos/:id

Пример теста на удаление:

describe("Delete one todo", () => {
  const newTodo = {
    id: crypto.randomUUID(),
    item: "To be deleted",
    completed: false,
  }

  beforeAll(async () => {
    await request(baseURL).post("/todo").send(newTodo);
  })

  it("should delete one item", async () => {
    const response = await request(baseURL).delete(`/todos/${newTodo.id}`);
    const todos = response.body.data;
    const exists = todos.find(todo => todo.id == newTodo.id);
    expect(exists).toBe(undefined);
  });
});

Внимание: в реализации DELETE важно удалять по индексу найденного элемента, а не по id как числу, если id — строка.

Практические советы и шаблоны

  • Запускайте сервер в режиме тестов: используйте отдельный порт или экспортируйте app и подключайте через SuperTest без слушания порта.
  • Деляйте тесты на unit и интеграционные: unit — без сети/БД, интеграционные — с реальным сервером/тестовой БД.
  • Фикстуры: храните шаблоны тестовых данных в отдельной папке fixtures/.
  • Идемпотентность: каждый тест должен убирать за собой данные (setup/teardown).
  • Таймауты: если тесты асинхронные, задавайте разумные таймауты в Jest (jest.setTimeout).
  • Схема ответа: проверяйте минимальную схему (наличие data, error и полей внутри data).

Мини-методика для разработки тестов (3 шага):

  1. Определите контракт эндпойнта (метод, путь, тело запроса, структура ответа, статус-коды).
  2. Напишите позитивный тест (happy path) и 2–3 негативных (ошибки валидации, несуществующие ресурсы).
  3. Автоматизируйте запуск в CI и фиксируйте flakiness (нестабильность) тестов.

Чек-листы по ролям

Developer:

  • Экспортировать app для SuperTest
  • Подключить express.json()
  • Возвращать корректные HTTP-коды
  • Именовать маршруты последовательно

QA / Тестировщик:

  • Написать позитивные и негативные сценарии
  • Проверить кейсы гонок при параллельных запросах
  • Убедиться в idempotency для безопасных операций

DevOps / CI:

  • Запустить тесты в изолированном окружении
  • Настроить переменные окружения для тестовой БД
  • Добавить отчётность по покрытию тестов

План отладки и отката (инцидент)

  1. При падении теста локально — воспроизвести последовательность запросов через curl/Postman.
  2. Проверить логи сервера на исключения и стек-трейсы.
  3. Если проблема в тестовых данных — откатить фикстуры и повторно прогнать тесты.
  4. В CI — откатить последний мердж, пока не выяснена причина.

Критерии приёмки

  • Для каждого эндпойнта есть по крайней мере один позитивный и один негативный тест.
  • Тесты стабильны в CI (отсутствие флейков > 95% успешных прогонов).
  • API возвращает ожидаемые HTTP-коды и корректную структуру JSON.

Частые ошибки и подводные камни

  • Не подключён express.json() — req.body будет undefined.
  • Тесты зависят от состояния (неочищаемые данные) — приводят к флейкам.
  • Использование одного и того же id в параллельных тестах — гонки.
  • Неправильная обработка ошибок: бросать исключения вместо возврата корректного кода (404/400).

Краткий глоссарий

  • Jest — тест-раннер и библиотека утверждений.
  • SuperTest — инструмент для отправки HTTP-запросов в тестах.
  • Фикстура — предустановленные данные для теста.
  • Идемпотентность — свойство операции давать одинаковый результат при повторных вызовах.

Итог

Вы научились конфигурировать Jest и SuperTest для тестирования простого Express REST API, писать тесты для CRUD-эндпойнтов и применять практики подготовки окружения и очистки данных. Следуйте чек-листам, автоматизируйте тесты в CI и переключайтесь на тестовую БД для более реалистичных интеграционных проверок.

Важное: начните с простых позитивных тестов и постепенно добавляйте негативные сценарии и стресс-тесты.

Ключевые ссылки в материале: экспорт app для SuperTest, использование express.json(), использование crypto.randomUUID() для уникальных id.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство