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

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

6 min read Node.js Обновлено 06 Apr 2026
Тестирование Express API с Jest и SuperTest
Тестирование Express API с Jest и SuperTest

Галочка на жёлтой бумажной заметке

Введение

Тестирование может занимать время, но это обязательный этап разработки. Оно помогает находить ошибки на ранней стадии до релиза в продакшн. Здесь вы научитесь писать тесты для простого CRUD API на Express с использованием Jest (фреймворк тестирования) и SuperTest (HTTP-клиент для тестов).

Важно: пример использует локальный сервер на порту 3000 и хранение данных в оперативной памяти (массив). В реальных проектах вместо массива используют тестовую БД или мок объекты.

Что такое Jest

Jest — популярный фреймворк тестирования на JavaScript. Его проще всего начать использовать. Изначально создан Facebook для тестирования React, но прекрасно работает с Node.js и любыми JS-проектами. Jest включает в себя свой набор утверждений (assertions) и структуру для описания тестов (describe, test/it, expect).

Коротко о терминологии:

  • Тест (test/it) — единичная проверка поведения.
  • Сьют (describe) — группа тестов.
  • Setup/Teardown (beforeAll, afterAll и т.д.) — подготовка и очистка окружения.

Что такое SuperTest

SuperTest — библиотека для тестирования HTTP в Node.js. Она строится на базе superagent и предоставляет объект request, который упрощает вызовы GET/POST/PUT/DELETE и получение ответа.

Пример использования 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, цепляете HTTP-метод и вызываете end() или используете async/await с .then(). Ответ затем проверяют с помощью Jest.

Создаём простой Express API

Для тестирования сначала нужно API. В примере API хранит задачи в массиве и поддерживает базовые операции CRUD.

Создайте папку и инициализируйте npm:

mkdir node-jest
npm init -y

Создайте файл index.js и запустите сервер:

const express = require("express")
const app = express()
app.use(express.json())
app.listen(3000, () => console.log("Listening at port 3000"))

Добавим массив задач и маршруты далее по тексту.

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

Этот endpoint возвращает все задачи. В index.js добавьте:

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

Здесь ответ содержит статус 200 и JSON с полем data — массив задач, и поле error = null. Это то, что мы будем проверять.

Установите Jest и SuperTest:

npm install jest supertest

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

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

Быстрый пример базового теста в Jest

Рассмотрим простую функцию и её тест:

function sum(a, b) {
  return a + b;
}

module.exports = sum;

Файл теста:

const sum = require("./sum")

describe("Sum of two items", () => {
  test("It should return 4", () => {
    expect(sum(2,2)).toBe(4)
  })
})

describe группирует тесты, test/it описывает конкретный случай, expect проверяет результат.

Тестовый файл для API

Создайте api.test.js. Jest автоматически найдёт файлы с суффиксами .test или .spec.

В тесте используем SuperTest и указываем базовый URL:

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

Пример теста для GET /todos с подготовкой данных:

describe("GET /todos", () => {
  const newTodo = {
    id: crypto.randomUUID(),
    item: "Drink water",
    completed: false,
  }
  beforeAll(async () => {
    // set up the todo
    await request(baseURL).post("/todo").send(newTodo);
  })
  afterAll(async () => {
    await request(baseURL).delete(`/todo/${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

Реализация эндпоинта в index.js:

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,
    });
  }
});

Тест отправляет тело запроса через send():

describe("POST /todo", () => {
  const newTodo = {
    // todo
  }
  afterAll(async () => {
    await request(baseURL).delete(`/todo/${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"]);
  });
});

Проверяем код ответа 201 и то, что последний элемент массива совпадает с отправленным.

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

Пример реализации в index.js:

app.put("/todos/:id", (req, res) => {
  try {
    const id = req.params.id
    const todo = todos.find((todo) => todo.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,
    });
  }
});

Тест обновления:

describe("Update one todo", () => {
  const newTodo = {
    // todo
  }
  beforeAll(async () => {
    await request(baseURL).post("/todo").send(newTodo);
  })
  afterAll(async () => {
    await request(baseURL).delete(`/todo/${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);
  });
});

Проверяем, что поле completed стало true.

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

Реализация в index.js:

app.delete("/todos/:id", (req, res) => {
  try {      
    const id = req.params.id
    const todo = todos[0]
    if(todo) {
      todos.splice(id, 1)
    }
    return res.status(200).json({
      data: todos,
      error: null,
    });
  } catch (error) {
    return res.status(500).json({
      data: null,
      error: error,
    });
  }
});

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

describe("Delete one todo", () => {
  const newTodo = {
    // todo
  }
  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 => {
      newTodo.id == todoId
    })
    expect(exists).toBe(undefined)
  });
});

Проверяем, что массив в ответе не содержит удалённой задачи.

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

  • Не забывайте парсить JSON в Express: app.use(express.json()). Без этого req.body будет undefined.
  • В тестах часто забывают запускать сервер до запуска тестов. Убедитесь, что сервер поднят (или экспортируйте app и запускайте его в тестовом окружении).
  • Для параллельного запуска тестов используйте разные порты или мокируйте сетевые вызовы.
  • Для интеграционных тестов применяйте тестовую базу данных или в памяти воссоздавайте состояние.

Важно: хранить данные только в оперативной памяти безопасно для демо. Для реальных приложений используйте отдельную тестовую БД и очищайте её между тестами.

Альтернативные подходы

  • Mocha + Chai + SuperTest. Mocha предоставляет рантайм, Chai — утверждения. Это гибкая связка.
  • AVA для быстрой параллельной работы тестов.
  • Playwright / Cypress для end-to-end тестов с браузером.

Выбор зависит от уровня тестирования: unit, integration, e2e.

Ментальные модели и эвристики

  • Тестовая пирамида: много unit-тестов, меньше интеграционных, ещё меньше e2e.
  • Отделяйте тесты по областям: логика отдельно от API, API отдельно от интеграций с БД.
  • Каждый тест должен быть детерминированным — одинаковый результат при одинаковом входе.

Уровни зрелости тестов

  1. Базовый: unit-тесты функций. Много маленьких тестов.
  2. Средний: интеграционные тесты API с моками для внешних сервисов.
  3. Высокий: end-to-end тесты с реальной БД и развёрнутым окружением в CI.

Чеклист перед добавлением теста

  • Тест покрывает один сценарий.
  • Нет побочных эффектов на внешний стейт.
  • Данные теста изолированы и очищаются.
  • Выполнение теста детерминировано.
  • Время выполнения теста разумное (желательно < 200–500 мс для unit).

Примеры шаблонов и сниппеты

Подготовка тестового сервера (экспорт app в index.js):

// index.js
const express = require("express")
const app = express()
app.use(express.json())

// экспортируем app для тестов
module.exports = app

if (require.main === module) {
  app.listen(3000, () => console.log("Listening at port 3000"))
}

Тогда в тестах можно подключать app напрямую и запускать SuperTest без поднятия внешнего порта:

const request = require("supertest")
const app = require("./index")

describe("API tests without external server", () => {
  it("should return 200", async () => {
    const res = await request(app).get("/todos")
    expect(res.statusCode).toBe(200)
  })
})

Это упрощает CI и устраняет гонки портов.

Критерии приёмки для API тестов

  • Тесты проходят локально и в CI без ручного вмешательства.
  • Для каждого endpoint есть сценарии успешного выполнения и основных ошибок.
  • Тесты покрывают граничные значения (пустые данные, неверные id, неправильные типы).
  • Тесты запускаются быстро при изменениях логики.

Когда такой подход не сработает

  • Когда API зависит от внешних сервисов с нестабильным откликом — потребуется мока/стаб.
  • Для высоконагруженных интеграционных тестов потребуется развёрнутая инфраструктура и тестовые дампы БД.

Интеграция в CI и советы

  • Запускайте тесты в CI на каждом PR.
  • Используйте отдельную ветку/пул для тестовой БД или Docker-контейнеры.
  • Параллелите тесты, но убедитесь, что они независимы по данным.

Рекомендуемые тест-кейсы (минимум)

  • GET /todos возвращает массив даже если он пуст.
  • POST /todo с валидными данными возвращает 201 и содержит добавленный элемент.
  • POST /todo с неполными данными возвращает 4xx и понятную ошибку.
  • PUT /todos/:id меняет существующий объект и возвращает обновлённый объект.
  • PUT /todos/:id для несуществующего id возвращает 404 или понятную ошибку.
  • DELETE /todos/:id удаляет объект и он отсутствует в ответе.

Частая проверка кода ответа и структуры

Используйте не только статус-коды, но и форму тела ответа:

  • statusCode должен быть ожидаемым (200, 201, 400, 404, 500).
  • response.body должен иметь поля data и error независимо от результата.
  • Для ошибок проверяйте, что error содержит информативную информацию.

Заключение

Вы научились писать базовые тесты для Express REST API с помощью Jest и SuperTest. Мы показали реализацию и тесты для GET, POST, PUT и DELETE, обсудили setup/teardown, альтернативные инструменты, чеклисты и рекомендации по CI. Примените эти шаблоны в своём проекте и расширяйте тесты по мере роста приложения.

Краткое резюме:

  • Пишите детерминированные тесты и изолируйте данные.
  • Используйте экспорт app для тестов без поднятия отдельного сервера.
  • Подключайте тесты в CI и очищайте тестовую среду.

Важно: в production-проектах избегайте хранения тестовых данных в основной БД и используйте контейнеры или отдельные инстансы для тестов.

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

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

Как открыть WebP в Windows 11
Windows

Как открыть WebP в Windows 11

Субтитры и аудиоописания в приложении Apple TV
Доступность

Субтитры и аудиоописания в приложении Apple TV

Poe от Quora: обзор и руководство по созданию ботов
AI и инструменты

Poe от Quora: обзор и руководство по созданию ботов

Как защитить ноутбук от кражи
Безопасность

Как защитить ноутбук от кражи

График с двумя осями Y в Google Sheets
Таблицы Google

График с двумя осями Y в Google Sheets

Как обновить Windows 8.1 до Windows 11
Windows

Как обновить Windows 8.1 до Windows 11