Интеграционное тестирование компонентов на базе Lit направлено на проверку их взаимодействия друг с другом и с внешними зависимостями, включая события, DOM и состояние приложения. В отличие от модульного тестирования, где тестируется отдельный компонент изолированно, интеграционные тесты оценивают корректность работы компонентов в составе страницы или приложения.
Тестирование взаимодействий, а не внутренней реализации Тесты должны проверять поведение компонентов снаружи: корректное отображение DOM, реакцию на события, изменения состояния. Внутренние методы и приватные свойства не являются объектом интеграционного тестирования.
Изоляция внешних сервисов При взаимодействии с API или сторонними библиотеками используется мокирование или стабы. Это позволяет концентрироваться на взаимодействиях компонентов, а не на стабильности внешних систем.
Воспроизводимость и предсказуемость Каждый тест должен создавать собственный чистый DOM-элемент и очищать его после выполнения, чтобы избежать побочных эффектов.
Для интеграционных тестов компонентов Lit часто используют следующие инструменты:
Пример базовой настройки:
import { fixture, html, expect } from '@open-wc/testing';
import './my-component.js';
describe('MyComponent integration', () => {
it('взаимодействует с дочерним компонентом', async () => {
const el = await fixture(html`<my-component></my-component>`);
const child = el.shadowRoot.querySelector('child-component');
expect(child).to.exist;
});
});
В Lit компоненты обычно обмениваются данными через:
el.someProp = 'value';
await el.updateComplete;
expect(el.shadowRoot.querySelector('span').textContent).to.equal('value');
const spy = sinon.spy();
el.addEventListener('custom-event', spy);
el.dispatchEvent(new CustomEvent('custom-event', { detail: 42 }));
expect(spy).to.have.been.calledOnceWith(sinon.match.has('detail', 42));
const el = await fixture(html`
<parent-component>
<child-component slot="item"></child-component>
</parent-component>
`);
const slotted = el.querySelector('child-component');
expect(slotted).to.exist;
updateCompleteLit использует реактивную систему обновлений, поэтому важно учитывать асинхронность рендера. Любая проверка DOM должна выполняться после завершения обновления:
el.prop = 'new value';
await el.updateComplete;
expect(el.shadowRoot.querySelector('span').textContent).to.equal('new value');
Для тестирования цепочек событий и сложных реакций может понадобиться
несколько последовательных await el.updateComplete.
Для интеграционных тестов часто нужно изолировать внешние сервисы:
import sinon from 'sinon';
import { fetchData } from './api.js';
const fetchStub = sinon.stub(window, 'fetch').resolves(new Response(JSON.stringify({ id: 1 })));
await el.loadData();
await el.updateComplete;
expect(el.data.id).to.equal(1);
fetchStub.restore();
Такой подход позволяет проверять корректность работы компонента при любых состояниях внешнего API без фактических сетевых запросов.
fixture и удаление элементов после теста
предотвращает утечки состояния.it('проверяет взаимодействие родителя и дочернего компонента с событием', async () => {
const el = await fixture(html`<parent-component></parent-component>`);
const child = el.shadowRoot.querySelector('child-component');
const result = [];
el.addEventListener('child-action', (e) => result.push(e.detail));
child.dispatchEvent(new CustomEvent('child-action', { detail: 'test' }));
await el.updateComplete;
expect(result).to.deep.equal(['test']);
expect(el.shadowRoot.querySelector('span').textContent).to.equal('test');
});
Такой тест одновременно проверяет правильность генерации события дочерним компонентом, обработку события родителем и обновление DOM, что полностью соответствует цели интеграционного тестирования в Lit.
Интеграционное тестирование обеспечивает уверенность в том, что компоненты корректно взаимодействуют между собой и с внешними системами, при этом оставаясь независимыми от конкретной реализации внутренних методов.