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

Тестирование 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
Автор
Редакция

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

Добавить текстуру к тексту в Photoshop
Дизайн

Добавить текстуру к тексту в Photoshop

Как очистить и продезинфицировать пульт от телевизора
Домашняя гигиена

Как очистить и продезинфицировать пульт от телевизора

Включить NumLock при запуске Windows 10
Windows

Включить NumLock при запуске Windows 10

Включить Emoji 15 на Windows 11
Windows

Включить Emoji 15 на Windows 11

Unity Lights: циферблат Apple Watch для Black History Month
Apple Watch

Unity Lights: циферблат Apple Watch для Black History Month

Обновление видеодрайвера для Rainbow Six Siege
Windows

Обновление видеодрайвера для Rainbow Six Siege