Очистка состояния между тестами

В тестировании на JavaScript важно обеспечивать изолированность тестов. Каждый тест должен выполняться в предсказуемой среде, не зависящей от изменений, внесённых другими тестами. Jasmine предоставляет для этого встроенные механизмы — хуки, позволяющие подготовить и очистить состояние перед или после выполнения тестов.


beforeEach и afterEach

beforeEach выполняется перед каждым тестом (it) внутри блока describe. Обычно используется для подготовки состояния: инициализации объектов, создания мока, очистки коллекций.

describe('Работа с массивом', () => {
  let items;

  beforeEach(() => {
    items = [];
  });

  it('добавляет элемент в массив', () => {
    items.push('apple');
    expect(items.length).toBe(1);
  });

  it('начинается с пустого массива', () => {
    expect(items.length).toBe(0);
  });
});

В примере массив items создаётся заново перед каждым тестом. Даже если первый тест что-то добавил, второй тест начнёт с чистого состояния.

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

describe('Манипуляции с DOM', () => {
  let container;

  beforeEach(() => {
    container = document.createElement('div');
    document.body.appendChild(container);
  });

  afterEach(() => {
    document.body.removeChild(container);
    container = null;
  });

  it('вставляет элемент в контейнер', () => {
    const child = document.createElement('span');
    container.appendChild(child);
    expect(container.children.length).toBe(1);
  });
});

Использование afterEach гарантирует, что DOM не будет загрязнён предыдущими тестами, что критично при тестировании фронтенда.


Иерархия хуков

Jasmine поддерживает вложенные блоки describe, и хуки работают с учётом иерархии:

describe('Внешний блок', () => {
  beforeEach(() => console.log('Внешний beforeEach'));

  describe('Внутренний блок', () => {
    beforeEach(() => console.log('Внутренний beforeEach'));
    it('тест', () => {});
  });
});

Последовательность вызова:

  1. Внешний beforeEach
  2. Внутренний beforeEach
  3. Тело it
  4. Внутренний afterEach
  5. Внешний afterEach

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


Использование с асинхронным кодом

В тестах с промисами, таймерами или асинхронными операциями важно корректно очищать состояние после завершения асинхронных действий. beforeEach и afterEach поддерживают колбэк done или возврат промиса.

describe('Асинхронная работа', () => {
  let value;

  beforeEach((done) => {
    setTimeout(() => {
      value = 42;
      done();
    }, 100);
  });

  afterEach(() => {
    value = null;
  });

  it('значение установлено асинхронно', () => {
    expect(value).toBe(42);
  });
});

Если используется async/await, хуки могут быть асинхронными функциями:

beforeEach(async () => {
  value = await fetchData();
});

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


Очистка глобального состояния

Иногда тесты модифицируют глобальные объекты, например window, localStorage или конфигурации библиотек. В таких случаях важно восстанавливать исходное состояние, чтобы другие тесты не ломались.

describe('Изменение глобальных настроек', () => {
  let originalTimeout;

  beforeEach(() => {
    originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
  });

  afterEach(() => {
    jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
  });

  it('тест с изменённым таймаутом', () => {
    expect(jasmine.DEFAULT_TIMEOUT_INTERVAL).toBe(5000);
  });
});

Без afterEach изменение таймаута повлияло бы на все последующие тесты.


Советы по эффективной очистке состояния

  • Минимизировать зависимость от глобальных переменных: создавать локальные объекты в beforeEach.
  • Использовать afterEach для сложных объектов и ресурсов: DOM, таймеры, подписки на события.
  • Не пытаться делить состояние между тестами: каждый тест должен быть независимым.
  • Асинхронная очистка: возвращать промисы или использовать done для гарантии корректного завершения.

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