Интеграционные тесты с DOM

Stimulus — это JavaScript-фреймворк, ориентированный на минимальное вмешательство в HTML-разметку и расширение возможностей браузера через controllers. Тестирование компонентов Stimulus требует особого подхода, так как поведение контроллеров напрямую связано с DOM.

Интеграционные тесты в контексте Stimulus проверяют взаимодействие контроллера с элементами DOM, события и реакции на изменения атрибутов. Они не ограничиваются единичными методами, а охватывают цепочку действий: от инициализации контроллера до обновления состояния интерфейса.


Подготовка окружения для тестирования

Для тестирования контроллеров Stimulus обычно используют Jest или Mocha вместе с библиотеками для работы с DOM, например jsdom. Важной задачей является создание тестового HTML, который максимально повторяет структуру настоящей страницы.

Пример базового окружения:

import { Application } from "@hotwired/stimulus"
import ExampleController from "../controllers/example_controller"

document.body.innerHTML = `
  <div data-controller="example" data-example-value="42">
    <button data-action="click->example#doSomething">Click me</button>
    <span data-example-target="output"></span>
  </div>
`;

const application = Application.start()
application.register("example", ExampleController)

Ключевые моменты:

  • data-controller связывает элемент с контроллером.
  • data-action определяет, какое событие вызовет метод контроллера.
  • data-*-target задаёт точки привязки для манипуляций DOM.

Инициализация и проверка состояния контроллера

После регистрации контроллера в приложении Stimulus, важно убедиться, что контроллер корректно инициализируется:

test("Controller initializes with correct value", () => {
  const controllerElement = document.querySelector("[data-controller='example']")
  const controller = controllerElement.controller // доступ к контроллеру через объект Stimulus

  expect(controller.value).toBe("42")
  expect(controller.outputTarget.textContent).toBe("")
})

Выделенные моменты:

  • Значения из data-*-value автоматически доступны через свойства контроллера.
  • Target’ы должны быть корректно распознаны, иначе методы взаимодействия с DOM будут некорректны.

Симуляция событий и проверка реакций

Stimulus активно использует actions, поэтому важной частью интеграционного теста является эмуляция событий:

test("Click triggers doSomething method", () => {
  const button = document.querySelector("button")
  const output = document.querySelector("[data-example-target='output']")

  button.click()

  expect(output.textContent).toBe("Action performed")
})

Особенности:

  • Использование стандартного метода .click() для имитации пользовательского действия.
  • Проверка DOM на соответствие ожидаемому результату после срабатывания метода контроллера.

Тестирование реактивных изменений

Stimulus позволяет реагировать на изменение атрибутов через MutationObserver или value observers. Проверка таких реакций требует модификации атрибутов и ожидания обновления состояния:

test("Value change updates output", () => {
  const controllerElement = document.querySelector("[data-controller='example']")
  controllerElement.setAttribute("data-example-value", "100")

  const controller = controllerElement.controller
  controller.refresh() // вручную вызываем observer, если используется Jest с jsdom

  expect(controller.outputTarget.textContent).toBe("Value updated to 100")
})
  • MutationObserver в jsdom может не срабатывать автоматически, поэтому часто требуется вручную инициировать обновление.
  • Проверка состояния target’ов после изменения атрибутов демонстрирует правильное связывание данных и DOM.

Организация тестов и переиспользуемость

Для интеграционных тестов важно создавать функции для повторного создания DOM и инициализации приложений Stimulus:

function setupController(html, controllerClass) {
  document.body.innerHTML = html
  const application = Application.start()
  application.register("example", controllerClass)
  return document.querySelector("[data-controller='example']").controller
}

Преимущества:

  • Изоляция тестов, отсутствие зависимости от глобального DOM.
  • Лёгкая замена контроллера для проверки разных сценариев.
  • Упрощение повторного использования HTML-шаблонов.

Практические советы

  1. Минимизировать сложность HTML: тесты должны проверять контроллер, а не сложную верстку.
  2. Использовать target’ы для прямой проверки изменений вместо поиска элементов по классам или id.
  3. Отделять unit-тесты от интеграционных: unit-тест проверяет отдельный метод, интеграционный — цепочку взаимодействий с DOM.
  4. Контролировать асинхронные события: если метод использует setTimeout или fetch, применять await и jest.runAllTimers().
  5. Обнулять состояние DOM перед каждым тестом, чтобы избежать побочных эффектов.

Особенности взаимодействия с jsdom

  • jsdom не полностью имитирует поведение браузера, поэтому некоторые события (focus, hover) нужно симулировать вручную.
  • MutationObserver в Jest часто требует принудительного вызова observe() или использования MutationObserver.mock.
  • Для методов, изменяющих layout (например, getBoundingClientRect), необходимо создавать моки, чтобы контроллер получал корректные значения.

Выводы по методологии

Интеграционные тесты Stimulus строятся вокруг инициализации контроллера, эмуляции событий DOM и проверки target’ов и значений. Они демонстрируют реальное поведение интерфейса и позволяют выявлять ошибки в связке JavaScript и HTML. Стратегически важно поддерживать чистоту DOM, переиспользуемость HTML-шаблонов и корректное управление асинхронными процессами.