Асинхронность в тестах

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

Асинхронная природа Unpoly

Все основные методы Unpoly, такие как up.navigate, up.replace или up.append, выполняют сетевые запросы и обновляют страницу асинхронно. Это означает, что результат операции может быть недоступен сразу после вызова функции. Для корректного тестирования необходимо синхронизироваться с завершением этих действий.

Unpoly предоставляет встроенные события, которые позволяют отслеживать жизненный цикл асинхронных операций:

  • up:request — вызывается перед отправкой HTTP-запроса.
  • up:response — вызывается после получения ответа, но до обновления DOM.
  • up:fragment:inserted — вызывается после вставки обновлённого фрагмента в DOM.
  • up:complete — вызывается после завершения всех операций, связанных с запросом.

Для тестов наибольшее значение имеют события up:fragment:inserted и up:complete, так как они гарантируют, что изменения DOM уже произошли.

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

Unpoly позволяет работать с промисами для асинхронного контроля. Например, метод up.request возвращает объект Promise, который разрешается после завершения всех действий:

up.request('/users/42', { target: '#user-details' })
  .then(() => {
    // DOM обновлён, можно проверять содержимое
    const content = document.querySelector('#user-details').textContent;
    console.assert(content.includes('John Doe'));
  })
  .catch(error => {
    console.error('Ошибка при запросе:', error);
  });

Такой подход облегчает интеграцию с современными фреймворками тестирования, такими как Jest или Mocha, где асинхронные тесты определяются с использованием async/await или возвращаемых промисов:

test('Детали пользователя отображаются после запроса', async () => {
  await up.request('/users/42', { target: '#user-details' });
  const content = document.querySelector('#user-details').textContent;
  expect(content).toContain('John Doe');
});

Ожидание событий с помощью up.on

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

up.on('up:fragment:inserted', '#user-details', () => {
  const content = document.querySelector('#user-details').textContent;
  expect(content).toContain('John Doe');
});

document.querySelector('#load-user-btn').click();

В этом примере проверка выполняется только после того, как фрагмент с деталями пользователя вставлен в DOM.

Совмещение с таймерами и ожиданиями

Тестирование асинхронного поведения может потребовать дополнительных механизмов ожидания. В Unpoly для тестов можно использовать короткие задержки или функции вроде up.macro.wait (если интегрирована соответствующая утилита), чтобы убедиться, что все промежуточные анимации и отложенные изменения завершены:

await up.request('/notifications', { target: '#notifications' });
await new Promise(resolve => setTimeout(resolve, 100)); // ожидание анимации
const notifications = document.querySelectorAll('#notifications .notification');
expect(notifications.length).toBeGreaterThan(0);

Ловушки асинхронности

  • Несинхронная проверка DOM: вызов проверки сразу после up.navigate или up.replace без ожидания события или промиса приведёт к тому, что DOM ещё не обновлён.
  • События нескольких фрагментов: если один запрос обновляет несколько фрагментов, следует внимательно выбирать селектор события up:fragment:inserted, чтобы тест ждал именно нужного элемента.
  • Анимации и эффекты: Unpoly по умолчанию применяет плавные анимации при вставке фрагментов. При тестировании необходимо либо отключать их через up.motion(false), либо учитывать задержку.

Интеграция с Jest, Mocha и Cypress

  • Jest / Mocha: асинхронные тесты легко интегрируются с async/await и промисами.
  • Cypress: для e2e-тестирования Unpoly рекомендуется использовать встроенные команды cy.get с опцией .should('contain', ...), что автоматически ждет появления элементов, заменяя необходимость явного ожидания событий.
cy.get('#load-user-btn').click();
cy.get('#user-details').should('contain', 'John Doe');

Такой подход позволяет скрыть внутренние асинхронные детали Unpoly за абстракцией фреймворка тестирования.

Лучшие практики

  • Всегда использовать промисы или события для синхронизации тестов с завершением асинхронных операций.
  • Указывать селекторы событий максимально точно, чтобы не ловить вставку лишних фрагментов.
  • При интеграционных и e2e-тестах учитывать анимации и сетевые задержки.
  • Для юнит-тестов можно отключать анимации и использовать заглушки HTTP-запросов.

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