Тестирование Express приложений

Jest является популярным инструментом для тестирования в экосистеме JavaScript. Он предоставляет удобные возможности для написания юнит-тестов, интеграционных тестов и мокирования. В этой статье рассмотрено, как использовать Jest для тестирования приложений, построенных на фреймворке Express.

Подготовка тестовой среды

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

Установка необходимых пакетов

Для начала необходимо установить Jest и другие зависимости, которые могут понадобиться в процессе тестирования. Это можно сделать с помощью npm:

npm install --save-dev jest supertest
  • Jest — основной инструмент для написания и выполнения тестов.
  • Supertest — библиотека для тестирования HTTP запросов. Она будет полезна для проверки работы Express маршрутов.

После установки Jest нужно настроить его для работы с приложением. Для этого добавьте в package.json следующий скрипт:

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

Структура тестов

Важным моментом является правильная организация тестов. Обычно тесты размещаются в отдельной директории, например, __tests__, чтобы отличать их от обычного исходного кода.

/src
  /app.js
  /routes
    /user.js
  /controllers
    /userController.js
/__tests__
  /user.test.js

Тестирование Express маршрутов

Самый базовый тип тестирования для Express приложений — это тестирование маршрутов. Для этого можно использовать библиотеку Supertest, которая позволяет отправлять HTTP запросы и проверять ответы.

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

Допустим, в Express приложении есть маршрут для получения списка пользователей:

// app.js
const express = require('express');
const app = express();
const userRouter = require('./routes/user');

app.use('/users', userRouter);

module.exports = app;

Маршрут для получения пользователей:

// routes/user.js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');

router.get('/', userController.getUsers);

module.exports = router;

Контроллер:

// controllers/userController.js
exports.getUsers = (req, res) => {
  res.status(200).json([{ id: 1, name: 'John Doe' }]);
};

Теперь, чтобы протестировать этот маршрут с использованием Jest и Supertest, создадим файл теста:

// __tests__/user.test.js
const request = require('supertest');
const app = require('../src/app');

describe('GET /users', () => {
  it('should return a list of users', async () => {
    const response = await request(app).get('/users');

    expect(response.status).toBe(200);
    expect(response.body).toEqual([{ id: 1, name: 'John Doe' }]);
  });
});

В данном примере тестируется маршрут GET /users, который должен вернуть список пользователей с кодом статуса 200 и телом ответа в виде массива объектов.

Тестирование ошибок

Тестирование маршрутов, которые могут вернуть ошибки, является важной частью процесса. Например, если маршрут не находит пользователя в базе данных, он должен вернуть ошибку 404.

Допустим, у нас есть следующий контроллер:

// controllers/userController.js
exports.getUserById = (req, res) => {
  const user = getUserFromDatabaseById(req.params.id);
  
  if (!user) {
    return res.status(404).json({ message: 'User not found' });
  }
  
  res.status(200).json(user);
};

Для его тестирования создадим следующий тест:

// __tests__/user.test.js
describe('GET /users/:id', () => {
  it('should return a 404 if the user is not found', async () => {
    const response = await request(app).get('/users/999');

    expect(response.status).toBe(404);
    expect(response.body.message).toBe('User not found');
  });
});

Этот тест проверяет, что если пользователь с указанным id не найден, приложение вернет ошибку с кодом 404 и сообщением «User not found».

Мокирование зависимостей

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

Мокирование базы данных

Для мокирования базы данных можно использовать библиотеки, такие как jest.mock или sinon. Например, если в контроллере используется функция для извлечения пользователей из базы данных, можно замокировать её поведение для тестирования.

// controllers/userController.js
const { getUsersFromDatabase } = require('../services/database');

exports.getUsers = async (req, res) => {
  try {
    const users = await getUsersFromDatabase();
    res.status(200).json(users);
  } catch (error) {
    res.status(500).json({ message: 'Internal Server Error' });
  }
};

В тестах можно мокировать функцию getUsersFromDatabase:

// __tests__/user.test.js
jest.mock('../src/services/database', () => ({
  getUsersFromDatabase: jest.fn(),
}));

const { getUsersFromDatabase } = require('../src/services/database');
const request = require('supertest');
const app = require('../src/app');

describe('GET /users', () => {
  it('should return a list of users', async () => {
    getUsersFromDatabase.mockResolvedValue([{ id: 1, name: 'John Doe' }]);

    const response = await request(app).get('/users');

    expect(response.status).toBe(200);
    expect(response.body).toEqual([{ id: 1, name: 'John Doe' }]);
  });

  it('should return 500 if there is a database error', async () => {
    getUsersFromDatabase.mockRejectedValue(new Error('Database error'));

    const response = await request(app).get('/users');

    expect(response.status).toBe(500);
    expect(response.body.message).toBe('Internal Server Error');
  });
});

В данном примере, функция getUsersFromDatabase замещается на мок-реализацию, которая возвращает либо успешный результат, либо ошибку, в зависимости от сценария.

Интеграционное тестирование Express приложений

Интеграционное тестирование Express приложений помогает проверить, как различные части системы взаимодействуют друг с другом. В таких тестах важно проверить не только маршруты и их обработку, но и работу приложения в целом.

Пример интеграционного теста с базой данных

Для интеграционного тестирования можно использовать тестовую базу данных или мокирование запросов к реальной базе данных. Например, при работе с MongoDB можно использовать mongodb-memory-server, который позволяет запустить временную базу данных в памяти для тестов.

npm install --save-dev mongodb-memory-server

Пример теста:

// __tests__/integration.test.js
const request = require('supertest');
const { MongoMemoryServer } = require('mongodb-memory-server');
const mongoose = require('mongoose');
const app = require('../src/app');

let mongoServer;

beforeAll(async () => {
  mongoServer = await MongoMemoryServer.create();
  const mongoUri = mongoServer.getUri();
  await mongoose.connect(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true });
});

afterAll(async () => {
  await mongoose.disconnect();
  await mongoServer.stop();
});

describe('GET /users', () => {
  it('should return a list of users from the database', async () => {
    await User.create({ name: 'John Doe' });

    const response = await request(app).get('/users');

    expect(response.status).toBe(200);
    expect(response.body.length).toBeGreaterThan(0);
  });
});

В данном примере создается временная база данных MongoDB, которая используется для интеграционных тестов, проверяя, как приложение взаимодействует с реальной базой данных.

Заключение

Тестирование Express приложений с использованием Jest позволяет эффективно проверять как отдельные части приложения, так и их взаимодействие. Использование таких инструментов как Supertest для HTTP тестирования и мокирование зависимостей, например, базы данных, делает процесс тестирования более гибким и удобным.