Настройка тестовой БД

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

Выбор базы данных

Для начала необходимо выбрать, какая база данных будет использоваться для тестирования. Часто в тестовых сценариях применяются те же СУБД, что и в рабочем приложении, но с небольшими различиями. В большинстве случаев для тестирования выбираются те же технологии, что и в продакшн-версии приложения (например, MongoDB, PostgreSQL, MySQL). Однако, в некоторых случаях можно использовать легковесные и быстрые решения, такие как SQLite, если приложение не зависит от специфичных функций основной СУБД.

Использование отдельных баз данных для тестирования

Одним из самых эффективных способов изоляции тестов является использование отдельной базы данных, специально предназначенной для тестирования. Это гарантирует, что данные, созданные в процессе тестирования, не окажут влияния на рабочие данные.

Пример настройки для MongoDB:

const mongoose = require('mongoose');
const dbURI = process.env.NODE_ENV === 'test' ? 'mongodb://localhost/test_db' : 'mongodb://localhost/main_db';

mongoose.connect(dbURI, { useNewUrlParser: true, useUnifiedTopology: true });

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

Мокирование и создание фиктивных данных

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

Для работы с моками в сочетании с Mocha можно использовать такие библиотеки, как sinon и mockgoose. С помощью этих библиотек можно подменить реальные запросы к базе данных на моки, что значительно ускоряет процесс тестирования и снижает нагрузку на реальные СУБД.

Пример мокирования с использованием sinon:

const sinon = require('sinon');
const MyModel = require('../models/myModel');

describe('MyModel', function () {
  let sandbox;
  
  beforeEach(function () {
    sandbox = sinon.createSandbox();
    sandbox.stub(MyModel, 'find').returns([{ name: 'Test' }]);
  });

  afterEach(function () {
    sandbox.restore();
  });

  it('should return mock data', async function () {
    const data = await MyModel.find();
    assert.deepEqual(data, [{ name: 'Test' }]);
  });
});

В этом примере метод find модели MyModel заменяется на мок, который всегда возвращает один и тот же результат. Это позволяет избежать реального взаимодействия с базой данных и тестировать логику работы с данными.

Чистка данных после каждого теста

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

Можно использовать метод afterEach, чтобы гарантировать, что база данных будет очищена после каждого теста. Для этого можно воспользоваться методами удаления данных, например, для MongoDB:

const MyModel = require('../models/myModel');

afterEach(async function () {
  await MyModel.deleteMany({});
});

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

Использование транзакций для отката изменений

В случае использования реляционных СУБД (например, PostgreSQL или MySQL) можно использовать транзакции для оборачивания всех изменений, внесенных в базу данных во время теста. В конце теста транзакция откатывается, и база данных остается в неизменном состоянии.

Пример настройки транзакций с использованием PostgreSQL и библиотеки pg:

const { Client } = require('pg');
const client = new Client();

describe('Database tests', function () {
  let client;
  let transaction;

  beforeEach(async function () {
    client = new Client();
    await client.connect();
    transaction = await client.query('BEGIN');
  });

  afterEach(async function () {
    await client.query('ROLLBACK');
    await client.end();
  });

  it('should create a new user', async function () {
    await client.query('INSERT INTO users(name) VALUES($1)', ['Test User']);
    const result = await client.query('SELECT * FROM users WHERE name=$1', ['Test User']);
    assert.strictEqual(result.rows.length, 1);
  });
});

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

Использование Docker для изолированного окружения

Для повышения надежности тестов и гарантии их выполнения в изолированном окружении можно использовать Docker для поднятия контейнера с базой данных. Это особенно полезно для тестирования с различными версиями СУБД или для тестирования в условиях, максимально приближенных к продакшн-среде.

Пример использования Docker для PostgreSQL:

docker run --name postgres-test -e POSTGRES_PASSWORD=mysecretpassword -d postgres

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

docker stop postgres-test
docker rm postgres-test

Этот подход гарантирует, что тесты выполняются в независимом и предсказуемом окружении.

Оптимизация производительности

Когда база данных используется для тестов, важно учесть, что выполнение операций с реальной базой может быть медленным, особенно при большом количестве данных. В таких случаях стоит подумать об оптимизации тестов, например, выполняя их на небольшой подмножества данных или используя mock-сервисы для части запросов. Также можно использовать инструменты для параллельного выполнения тестов, чтобы ускорить общий процесс тестирования.

Для этого стоит исследовать возможности параллельного выполнения тестов в Mocha с использованием дополнительных библиотек, например, mocha-parallel-tests, которая позволяет выполнять тесты параллельно, разделяя тестовые базы данных.

Заключение

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