Юнит-тестирование является важным аспектом разработки веб-приложений с использованием Stimulus. Контроллеры в этом фреймворке отвечают за связывание логики JavaScript с элементами DOM, и тестирование их поведения позволяет гарантировать корректность работы интерактивных компонентов.
Stimulus строится на принципе декларативного связывания
данных с элементами DOM через data-controller,
data-action и data-target. Юнит-тестирование
фокусируется на проверке:
Контроллеры Stimulus являются классами, наследующимися от
Stimulus.Controller, что упрощает их тестирование через
создание экземпляра контроллера и имитацию DOM-структуры.
Для юнит-тестирования чаще всего используют Jest или
Vitest, поскольку они предоставляют поддержку
виртуального DOM через jsdom. Основные шаги:
npm install --save-dev jest @testing-library/dom @testing-library/jest-dom
{
"testEnvironment": "jsdom",
"setupFilesAfterEnv": ["@testing-library/jest-dom/extend-expect"]
}
<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;Контроллеры часто взаимодействуют с внешними сервисами. Для юнит-тестов важно мокировать эти зависимости, чтобы тест оставался изолированным.
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-процессы.
Достаточно настроить запуск Jest с опцией --coverage для
контроля покрытия кода:
npx jest --coverage
Это позволяет отслеживать, какие части контроллера не покрыты тестами, и поддерживать высокий уровень качества кода.
Юнит-тестирование в Stimulus позволяет создавать устойчивые и предсказуемые интерактивные интерфейсы, минимизируя ошибки, связанные с динамическим обновлением DOM и обработкой событий. Такой подход облегчает масштабирование приложений и обеспечивает уверенность в стабильности контроллеров при изменениях кода.