Интеграционное тестирование

Интеграционное тестирование в контексте Riot.js направлено на проверку взаимодействия компонентов, тегов, сторов и внешних зависимостей как единого целого. В отличие от модульных тестов, здесь акцент смещается с изолированной логики на корректность связей: передача данных через props, работа реактивных обновлений, обработка событий и жизненные циклы тегов.

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

  • родительские теги передают данные дочерним;
  • дочерние инициируют события;
  • состояние хранится во внешних сторах или сервисах;
  • асинхронные операции влияют на рендеринг.

Интеграционные тесты позволяют проверить, что:

  • теги корректно монтируются в DOM;
  • данные проходят по цепочке без искажений;
  • реактивность работает при изменении состояния;
  • пользовательские события приводят к ожидаемым изменениям интерфейса.

Инструментарий

Для интеграционного тестирования Riot.js чаще всего используется связка:

  • Jest — тестовый раннер и ассерты
  • @riotjs/dom-bindings — рендеринг тегов вне браузера
  • jsdom — эмуляция DOM
  • @testing-library/dom или @testing-library/jest-dom — работа с DOM на уровне пользователя

Riot.js не навязывает собственный тестовый фреймворк, поэтому экосистема JavaScript используется напрямую.

Подготовка тестовой среды

Для корректной работы интеграционных тестов необходимо эмулировать браузерную среду:

import { component } from 'riot'
import MyApp from '../src/my-app.riot'

document.body.innerHTML = '<div id="root"></div>'

Jest по умолчанию использует jsdom, что позволяет:

  • монтировать теги в document;
  • проверять HTML-структуру;
  • эмулировать события.

Монтирование и взаимодействие компонентов

Базовый сценарий интеграционного теста — монтирование корневого тега и проверка его дочерних элементов:

test('приложение рендерит список элементов', () => {
  const mount = component(MyApp)
  const instance = mount(document.getElementById('root'), {
    items: ['A', 'B', 'C']
  })

  const listItems = document.querySelectorAll('li')
  expect(listItems.length).toBe(3)
})

Здесь проверяется сразу несколько аспектов:

  • корректность props;
  • генерация DOM-элементов;
  • шаблонная логика.

Проверка реактивных обновлений

Одно из ключевых свойств Riot.js — реактивность на основе прокси. Интеграционные тесты должны подтверждать, что изменения состояния приводят к обновлению интерфейса.

instance.update({ items: ['A', 'B', 'C', 'D'] })

const listItems = document.querySelectorAll('li')
expect(listItems.length).toBe(4)

Тест охватывает:

  • механизм update;
  • повторный рендер;
  • корректное обновление DOM без полной перезагрузки.

Тестирование событий и пользовательских действий

Riot.js активно использует события DOM и кастомные обработчики. Интеграционные тесты проверяют сценарии взаимодействия:

const button = document.querySelector('button')
button.click()

expect(document.querySelector('.counter').textContent).toBe('1')

В этом случае тест затрагивает:

  • шаблон с onclick;
  • внутреннее состояние тега;
  • визуальное отражение изменений.

Взаимодействие между родительскими и дочерними тегами

Типичный интеграционный сценарий — проверка передачи данных вниз и событий вверх:

<parent-tag>
  <child-tag value={state.count} onIncrem ent={increment} />
</parent-tag>

Тест проверяет целостность цепочки:

const childButton = document.querySelector('child-tag button')
childButton.click()

expect(document.querySelector('.parent-count').textContent).toBe('1')

Здесь одновременно тестируются:

  • передача props;
  • вызов callback-функции;
  • обновление состояния родителя;
  • повторный рендер обоих тегов.

Интеграция со сторами и внешним состоянием

При использовании глобального состояния или кастомных сторов важно проверять не сам стор, а его влияние на компоненты:

store.setState({ user: { name: 'Alex' } })

expect(document.querySelector('.username').textContent).toBe('Alex')

Такой тест подтверждает:

  • подписку тега на стор;
  • реакцию на изменение состояния;
  • синхронизацию данных между слоями.

Асинхронные сценарии

Интеграционные тесты часто включают асинхронную логику: загрузку данных, таймеры, промисы.

await instance.loadData()

await Promise.resolve() // ожидание обновления DOM

expect(document.querySelector('.status').textContent).toBe('Готово')

Важно учитывать:

  • асинхронный рендер;
  • необходимость ожидания microtask/macrotask очередей;
  • стабильность тестов при повторном запуске.

Изоляция интеграционных тестов

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

  • внешние API мокируются;
  • сетевые запросы подменяются;
  • таймеры контролируются через jest.useFakeTimers.

Это позволяет тестировать именно связность системы, а не внешнюю инфраструктуру.

Критерии качества интеграционных тестов

Хороший интеграционный тест в Riot.js:

  • проверяет пользовательский сценарий, а не внутреннюю реализацию;
  • охватывает несколько компонентов сразу;
  • устойчив к рефакторингу шаблонов;
  • минимально зависит от внутренней структуры DOM.

Интеграционное тестирование в Riot.js служит связующим звеном между модульными тестами и e2e-проверками, обеспечивая уверенность в том, что компоненты корректно взаимодействуют в реальной среде приложения.