Тестирование асинхронной логики

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

Асинхронные обновления компонентов

В Slim.js изменения состояния компонентов автоматически отражаются в DOM через реактивную систему. Это включает в себя и асинхронные операции. Рассмотрим пример:

class AsyncComponent extends Slim {
  constructor() {
    super();
    this.data = null;
  }

  get template() {
    return `<div>{{data}}</div>`;
  }

  async fetchData() {
    const response = await fetch('/api/data');
    const json = await response.json();
    this.data = json.value;
  }
}

customElements.define('async-component', AsyncComponent);

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

Тестирование с использованием промисов

Для проверки асинхронного поведения необходимо использовать промисы и ожидание их завершения. В Jest это делается с помощью async/await:

test('fetchData обновляет состояние компонента', async () => {
  const component = document.createElement('async-component');
  document.body.appendChild(component);

  // Подмена fetch для тестирования
  global.fetch = jest.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve({ value: 'Тестовое значение' }),
    })
  );

  await component.fetchData();

  expect(component.data).toBe('Тестовое значение');
  expect(component.shadowRoot.innerHTML).toContain('Тестовое значение');
});

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

Слежение за реактивными изменениями

Slim.js обновляет DOM не мгновенно, а через реактивную очередь изменений. Иногда необходимо дождаться следующего цикла обновления:

await new Promise(resolve => setTimeout(resolve, 0));

Это гарантирует, что все реактивные изменения, связанные с присвоением значений свойствам, будут применены к DOM перед проверкой.

Тестирование событий

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

class EventComponent extends Slim {
  constructor() {
    super();
    this.data = null;
  }

  get template() {
    return `<button on-click="loadData">Загрузить</button>`;
  }

  async loadData() {
    this.data = await Promise.resolve('Значение');
    this.dispatchEvent(new CustomEvent('data-loaded', { detail: this.data }));
  }
}

customElements.define('event-component', EventComponent);

test('loadData генерирует событие data-loaded', async () => {
  const component = document.createElement('event-component');
  document.body.appendChild(component);

  const listener = jest.fn();
  component.addEventListener('data-loaded', listener);

  await component.loadData();

  expect(listener).toHaveBeenCalledWith(expect.objectContaining({
    detail: 'Значение'
  }));
});

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

Моки и заглушки для асинхронных зависимостей

Для изоляции компонентов в тестах важно использовать моки. Fetch-запросы, таймеры и другие внешние ресурсы можно подменять на контролируемые объекты. Например, таймеры можно тестировать через Jest Fake Timers:

jest.useFakeTimers();

test('отложенное обновление данных', () => {
  const component = document.createElement('async-component');
  document.body.appendChild(component);

  component.data = 'Начальное';

  setTimeout(() => {
    component.data = 'Обновленное';
  }, 1000);

  jest.advanceTimersByTime(1000);

  expect(component.data).toBe('Обновленное');
});

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

Советы по устойчивым тестам

  • Всегда использовать await для асинхронных функций.
  • Дождаться реактивного обновления DOM перед проверкой содержимого.
  • Изолировать внешние зависимости через моки и заглушки.
  • Проверять события и побочные эффекты, а не только внутренние свойства.
  • Разбивать тесты на небольшие, предсказуемые блоки.

Асинхронное тестирование в Slim.js требует понимания реактивной природы компонентов и правильного управления промисами и событиями. Систематическое применение описанных подходов обеспечивает точные и надежные проверки поведения компонентов.