Хуки жизненного цикла: beforeEach, afterEach, beforeAll, afterAll

Jest предоставляет мощные инструменты для организации и управления тестами, среди которых важную роль играют хуки жизненного цикла. Хуки позволяют выполнить код до или после выполнения тестов, что полезно для настройки тестовой среды или очистки после тестов. В Jest доступны следующие хуки: beforeEach, afterEach, beforeAll, и afterAll. Каждый из них имеет свою область применения и используется в зависимости от необходимости.

beforeEach

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

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

describe('Тесты с beforeEach', () => {
  let counter;

  beforeEach(() => {
    counter = 0;
  });

  test('Тест 1: Проверка инкремента', () => {
    counter++;
    expect(counter).toBe(1);
  });

  test('Тест 2: Проверка инкремента еще раз', () => {
    counter++;
    expect(counter).toBe(1);
  });
});

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

afterEach

Хук afterEach выполняется после каждого теста в блоке. Его основное назначение — очистка или освобождение ресурсов, которые были задействованы в тестах. Это важно для предотвращения влияния побочных эффектов от одного теста на другой.

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

describe('Тесты с afterEach', () => {
  let db;

  beforeEach(() => {
    db = { connected: true };
  });

  afterEach(() => {
    db.connected = false;
  });

  test('Тест 1: Проверка соединения с базой данных', () => {
    expect(db.connected).toBe(true);
  });

  test('Тест 2: Проверка разъединения с базой данных', () => {
    expect(db.connected).toBe(true);
  });
});

Здесь после каждого теста база данных будет отсоединена, что гарантирует, что каждый тест будет начинаться с чистого состояния.

beforeAll

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

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

describe('Тесты с beforeAll', () => {
  let server;

  beforeAll(() => {
    server = startServer(); // Старт сервера, который нужен для всех тестов
  });

  test('Тест 1: Проверка ответа сервера', async () => {
    const response = await fetch(server.url);
    expect(response.status).toBe(200);
  });

  test('Тест 2: Проверка другого запроса к серверу', async () => {
    const response = await fetch(server.url + '/health');
    expect(response.status).toBe(200);
  });
});

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

afterAll

Хук afterAll выполняется один раз после завершения всех тестов в блоке. Это позволяет выполнить очистку или освободить ресурсы, которые не нужно держать после выполнения всех тестов. Например, можно остановить сервер или закрыть подключение к базе данных, если они были инициализированы в beforeAll.

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

describe('Тесты с afterAll', () => {
  let server;

  beforeAll(() => {
    server = startServer();
  });

  afterAll(() => {
    server.stop(); // Остановка сервера после всех тестов
  });

  test('Тест 1: Проверка ответа сервера', async () => {
    const response = await fetch(server.url);
    expect(response.status).toBe(200);
  });

  test('Тест 2: Проверка другого запроса к серверу', async () => {
    const response = await fetch(server.url + '/health');
    expect(response.status).toBe(200);
  });
});

В этом примере сервер будет остановлен после выполнения всех тестов, что важно для управления ресурсами и предотвращения утечек памяти или блокировок.

Использование хуков вместе

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

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

describe('Тесты с хуками жизненного цикла', () => {
  let db;

  beforeAll(() => {
    db = startDatabase();
  });

  beforeEach(() => {
    db.clear(); // Очищаем базу перед каждым тестом
  });

  afterEach(() => {
    db.rollback(); // Откатываем изменения после каждого теста
  });

  afterAll(() => {
    db.close(); // Закрываем соединение с базой данных после всех тестов
  });

  test('Тест 1: Проверка добавления записи в базу данных', () => {
    db.add({ id: 1, name: 'Item' });
    expect(db.find(1)).toEqual({ id: 1, name: 'Item' });
  });

  test('Тест 2: Проверка удаления записи из базы данных', () => {
    db.add({ id: 2, name: 'Item 2' });
    db.remove(2);
    expect(db.find(2)).toBeNull();
  });
});

В этом примере база данных проходит через несколько этапов: она инициализируется в beforeAll, очищается перед каждым тестом с помощью beforeEach, откатывается после каждого теста с помощью afterEach, и закрывается в afterAll. Это позволяет поддерживать чистоту и консистентность тестов.

Рекомендации по использованию

  • Минимизация дублирования кода: Хуки позволяют избежать дублирования логики и сделать тесты более компактными и читаемыми. Например, использование beforeEach помогает избежать повторного написания одной и той же инициализации в каждом тесте.
  • Оптимизация времени тестов: Хуки, такие как beforeAll и afterAll, позволяют существенно сократить время выполнения тестов, избегая повторной инициализации ресурсов.
  • Чистота и независимость тестов: Хуки помогают изолировать тесты друг от друга, гарантируя, что каждый тест будет запускаться в чистой среде. Это предотвращает влияние состояния одного теста на другие.

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