Юнит-тестирование контроллеров

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


Основы тестирования контроллеров

Stimulus строится на принципе декларативного связывания данных с элементами DOM через data-controller, data-action и data-target. Юнит-тестирование фокусируется на проверке:

  • Инициализации контроллера;
  • Взаимодействия с DOM-элементами;
  • Реакции на события;
  • Вызовов методов контроллера в ответ на действия пользователя.

Контроллеры Stimulus являются классами, наследующимися от Stimulus.Controller, что упрощает их тестирование через создание экземпляра контроллера и имитацию DOM-структуры.


Настройка окружения

Для юнит-тестирования чаще всего используют Jest или Vitest, поскольку они предоставляют поддержку виртуального DOM через jsdom. Основные шаги:

  1. Установить зависимости:
npm install --save-dev jest @testing-library/dom @testing-library/jest-dom
  1. Создать конфигурацию для Jest:
{
  "testEnvironment": "jsdom",
  "setupFilesAfterEnv": ["@testing-library/jest-dom/extend-expect"]
}
  1. Создать HTML-шаблон для теста:
<div data-controller="example">
  <button data-action="click->example#increment">Click</button>
  <span data-example-target="count">0</span>
</div>

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

Контроллер можно протестировать, создавая его экземпляр и привязывая к DOM-элементу:

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

let application;
let element;

beforeEach(() => {
  document.body.innerHTML = `
    <div data-controller="example">
      <span data-example-target="count">0</span>
    </div>
  `;
  element = document.querySelector("[data-controller='example']");
  application = Application.start();
  application.register("example", ExampleController);
});

Это позволяет эмулировать реальную среду работы контроллера.


Тестирование методов контроллера

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

Пример метода increment:

import { fireEvent } from "@testing-library/dom";

test("увеличивает счетчик при клике", () => {
  const button = document.createElement("button");
  button.setAttribute("data-action", "click->example#increment");
  element.appendChild(button);

  const controller = application.getControllerForElementAndIdentifier(element, "example");
  expect(element.querySelector("[data-example-target='count']").textContent).toBe("0");

  fireEvent.click(button);

  expect(element.querySelector("[data-example-target='count']").textContent).toBe("1");
});

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

  • Использование fireEvent для симуляции событий;
  • Получение экземпляра контроллера через getControllerForElementAndIdentifier;
  • Проверка изменений в DOM после вызова метода.

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

Контроллеры часто взаимодействуют с внешними сервисами. Для юнит-тестов важно мокировать эти зависимости, чтобы тест оставался изолированным.

jest.mock("../services/api", () => ({
  fetchData: jest.fn().mockResolvedValue({ count: 5 })
}));

test("получает данные из API и обновляет счетчик", async () => {
  const controller = application.getControllerForElementAndIdentifier(element, "example");
  await controller.loadData();
  expect(element.querySelector("[data-example-target='count']").textContent).toBe("5");
});

Тестирование жизненного цикла

Stimulus контроллеры имеют методы жизненного цикла: connect, disconnect, initialize. Их можно тестировать напрямую:

test("connect вызывается при инициализации", () => {
  const spy = jest.spyOn(ExampleController.prototype, "connect");
  const controller = application.getControllerForElementAndIdentifier(element, "example");
  expect(spy).toHaveBeenCalled();
});

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

  • initialize вызывается перед connect;
  • disconnect можно проверять, удаляя элемент из DOM:
element.remove();
expect(controller.element.isConnected).toBe(false);

Рекомендации по организации тестов

  • Каждый контроллер должен иметь отдельный файл с тестами;
  • Тесты должны покрывать как основные сценарии взаимодействия с пользователем, так и нештатные ситуации;
  • Использовать селекторы data-target и data-action вместо классов или ID для повышения устойчивости тестов к изменениям верстки;
  • Избегать тестирования сторонних библиотек внутри контроллера — юнит-тест проверяет только собственную логику.

Интеграция с CI/CD

Юнит-тесты контроллеров легко интегрируются в CI/CD-процессы. Достаточно настроить запуск Jest с опцией --coverage для контроля покрытия кода:

npx jest --coverage

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


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