Выполнение SQL-запросов

Cypress изначально ориентирован на end-to-end тестирование веб-приложений, однако часто возникает необходимость взаимодействовать с базой данных для подготовки тестовых данных или проверки состояния приложения. Прямого встроенного механизма работы с SQL в Cypress нет, поэтому для выполнения SQL-запросов применяются обходные подходы с использованием Node.js и внешних библиотек.


Подключение к базе данных

Для работы с базой данных из Cypress на уровне Node.js используется cy.task, который позволяет выполнять код на стороне Node, а не в браузере. Это критически важно, поскольку браузер не имеет доступа к файловой системе и сети напрямую для подключения к базе данных.

Пример подключения к базе данных MySQL:

// cypress/plugins/index.js
const mysql = require('mysql');

module.exports = (on, config) => {
  const dbConfig = {
    host: config.env.dbHost,
    user: config.env.dbUser,
    password: config.env.dbPassword,
    database: config.env.dbName
  };

  on('task', {
    queryDatabase({ sql, values }) {
      const connection = mysql.createConnection(dbConfig);
      return new Promise((resolve, reject) => {
        connection.connect(err => {
          if (err) return reject(err);
          connection.query(sql, values, (error, results) => {
            connection.end();
            if (error) return reject(error);
            resolve(results);
          });
        });
      });
    }
  });
};

Здесь ключевые моменты:

  • Использование cy.task для выполнения кода на Node.js.
  • Создание соединения к базе внутри задачи.
  • Возврат Promise для корректной интеграции с Cypress.

Выполнение SQL-запросов в тестах

После настройки задачи в plugins, SQL-запросы вызываются из тестов через cy.task. Пример запроса данных из таблицы users:

describe('Проверка пользователей', () => {
  it('Должен получить список пользователей', () => {
    cy.task('queryDatabase', { sql: 'SELECT * FROM users WHERE status = ?', values: ['active'] })
      .then(users => {
        expect(users).to.have.length.greaterThan(0);
        users.forEach(user => {
          expect(user.status).to.eq('active');
        });
      });
  });
});

Особенности:

  • values используются для безопасной подстановки параметров, предотвращая SQL-инъекции.
  • Результат запроса возвращается в тест в виде массива объектов.

Вставка, обновление и удаление данных

Cypress через cy.task позволяет не только читать данные, но и изменять их. Пример вставки нового пользователя:

cy.task('queryDatabase', {
  sql: 'INSERT INTO users (name, email, status) VALUES (?, ?, ?)',
  values: ['Иван Иванов', 'ivan@example.com', 'active']
}).then(result => {
  expect(result.affectedRows).to.eq(1);
});

Обновление и удаление аналогичны:

cy.task('queryDatabase', {
  sql: 'UPDATE users SE T status = ? WHERE id = ?',
  values: ['inactive', 5]
});

cy.task('queryDatabase', {
  sql: 'DELETE FROM users WHERE id = ?',
  values: [10]
});

Важно проверять affectedRows для подтверждения того, что операция затронула нужное количество записей.


Организация тестовых данных

Для комплексного тестирования часто используется подготовка базы перед выполнением тестов:

beforeEach(() => {
  cy.task('queryDatabase', { sql: 'DELETE FROM users WHERE test_flag = 1' });
  cy.task('queryDatabase', {
    sql: 'INSERT INTO users (name, email, status, test_flag) VALUES (?, ?, ?, 1)',
    values: ['Тестовый пользователь', 'test@example.com', 'active']
  });
});

Подход позволяет:

  • Создавать изолированное состояние базы перед каждым тестом.
  • Быстро сбрасывать тестовые данные после завершения тестов.
  • Минимизировать зависимость тестов друг от друга.

Работа с транзакциями

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

cy.task('queryDatabase', { sql: 'START TRANSACTION' });
cy.task('queryDatabase', {
  sql: 'INSERT INTO orders (user_id, total) VALUES (?, ?)',
  values: [1, 500]
});
cy.task('queryDatabase', { sql: 'ROLLBACK' });

Использование ROLLBACK гарантирует, что база вернется в исходное состояние, что критично для повторяемых тестов.


Логирование и обработка ошибок

При работе с SQL в Cypress важно отслеживать ошибки и логировать их для диагностики:

cy.task('queryDatabase', { sql: 'SELECT * FROM nonexistent_table' })
  .then(results => {
    // не будет вызвано
  })
  .catch(error => {
    cy.log('Ошибка SQL запроса: ' + error.message);
  });

Ключевые моменты:

  • Любые ошибки в SQL приводят к откату промиса и попаданию в catch.
  • Логирование ошибок помогает быстро выявить неправильные запросы или проблемы с соединением.

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

Для работы с другими базами, кроме MySQL, можно использовать:

  • PostgreSQL — пакет pg.
  • Microsoft SQL Server — пакет mssql.
  • SQLite — пакет sqlite3.

Принцип остается тем же: создается cy.task в плагинах, соединение открывается на стороне Node.js, результат возвращается в тест.


Практические рекомендации

  • Изоляция тестов: каждый тест должен создавать собственные данные и очищать их после выполнения.
  • Проверка affectedRows: позволяет убедиться, что запрос реально изменил базу.
  • Использование транзакций: особенно важно для тестов, где требуется многократное изменение данных.
  • Безопасная подстановка параметров: всегда использовать подготовленные значения (? и массив values) для предотвращения SQL-инъекций.

Работа с SQL через cy.task делает Cypress полноценным инструментом для интеграционного тестирования, позволяя контролировать состояние базы данных, проверять корректность бизнес-логики и поддерживать надежность тестов даже при сложной архитектуре приложения.