Unit тестирование с Jest и другими фреймворками

Unit-тестирование в контексте компонентов Lit сосредоточено на проверке их функциональности в изоляции. Основная цель — удостовериться, что состояние компонента, его методы и рендеринг работают корректно независимо от внешнего окружения. В Lit компоненты создаются с использованием класса, наследующегося от LitElement, и декларативного рендеринга через шаблоны html. Это накладывает свои особенности на подход к тестированию.

Структура тестируемого компонента

Пример базового компонента Lit:

import { LitElement, html, css } from 'lit';

export class MyCounter extends LitElement {
  static properties = {
    count: { type: Number }
  };

  constructor() {
    super();
    this.count = 0;
  }

  increment() {
    this.count += 1;
  }

  render() {
    return html`
      <div>
        <p>Count: ${this.count}</p>
        <button @click="${this.increment}">Increment</button>
      </div>
    `;
  }
}

customElements.define('my-counter', MyCounter);

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

  • Свойства и их реактивность.
  • Методы, изменяющие состояние.
  • Правильность рендеринга на основе состояния.
  • Обработка событий (например, клики по кнопкам).

Настройка Jest для тестирования Lit-компонентов

Для Jest необходима среда, поддерживающая работу с DOM. Обычно используют jsdom. Конфигурация в package.json может выглядеть так:

{
  "jest": {
    "testEnvironment": "jsdom",
    "transform": {
      "^.+\\.js$": "babel-jest"
    }
  }
}

Установка зависимостей:

npm install --save-dev jest babel-jest @babel/preset-env @web/test-runner @testing-library/dom

Babel-конфигурация (babel.config.js) для корректной трансформации ES-модулей:

module.exports = {
  presets: ['@babel/preset-env']
};

Тестирование рендеринга

Для проверки рендеринга используют библиотеку @testing-library/dom или @open-wc/testing. Пример с Jest и @testing-library/dom:

import { screen, fireEvent } from '@testing-library/dom';
import './my-counter.js';

describe('MyCounter', () => {
  let counter;

  beforeEach(() => {
    counter = document.createElement('my-counter');
    document.body.appendChild(counter);
  });

  afterEach(() => {
    document.body.removeChild(counter);
  });

  test('отрисовывает начальное значение count', () => {
    const paragraph = counter.shadowRoot.querySelector('p');
    expect(paragraph.textContent).toBe('Count: 0');
  });

  test('метод increment увеличивает count', () => {
    counter.increment();
    const paragraph = counter.shadowRoot.querySelector('p');
    expect(paragraph.textContent).toBe('Count: 1');
  });

  test('клик по кнопке вызывает increment', async () => {
    const button = counter.shadowRoot.querySelector('button');
    await fireEvent.click(button);
    const paragraph = counter.shadowRoot.querySelector('p');
    expect(paragraph.textContent).toBe('Count: 1');
  });
});

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

  • Использование shadowRoot для доступа к элементам компонента.
  • Очистка DOM после каждого теста для предотвращения побочных эффектов.
  • Проверка изменения состояния через методы и события.

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

Если компонент использует внешние сервисы или глобальные объекты, их рекомендуется мокировать, чтобы тест оставался изолированным.

jest.mock('../api.js', () => ({
  fetchData: jest.fn().mockResolvedValue({ value: 42 })
}));

После этого можно проверять корректность реакции компонента на полученные данные без реальных сетевых запросов.


Использование @open-wc/testing

Библиотека @open-wc/testing предоставляет удобные утилиты для тестирования веб-компонентов:

  • fixture — создание компонента в тестовом DOM.
  • html — шаблонный тег для декларативного рендеринга.
  • elementUpdated — ожидание завершения обновления компонента после изменения состояния.

Пример:

import { fixture, html, expect, elementUpdated } from '@open-wc/testing';
import './my-counter.js';

describe('MyCounter', () => {
  it('increment через событие кнопки', async () => {
    const el = await fixture(html`<my-counter></my-counter>`);
    el.shadowRoot.querySelector('button').click();
    await elementUpdated(el);
    expect(el.count).to.equal(1);
  });
});

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

  • Асинхронное ожидание обновления шаблона.
  • Удобная интеграция с Chai для выражений expect.
  • Меньше ручной работы с shadowRoot.

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

В Lit свойства объявляются через static properties. Проверка их работы критична для корректного рендеринга:

test('свойство count обновляет шаблон', async () => {
  const el = document.createElement('my-counter');
  document.body.appendChild(el);
  el.count = 5;
  await el.updateComplete;
  const paragraph = el.shadowRoot.querySelector('p');
  expect(paragraph.textContent).toBe('Count: 5');
});

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

  • updateComplete — промис, который завершается после полной отрисовки шаблона.
  • Позволяет корректно тестировать асинхронное обновление DOM при изменении реактивных свойств.

Интеграция с другими фреймворками тестирования

Помимо Jest и @open-wc/testing, возможны варианты с Mocha, Karma и Testing Library для веб-компонентов. Основной принцип сохраняется: создание компонента, управление состоянием, проверка DOM и событий. Различия заключаются в API ассертов и запуске тестов в браузере или Node.js с jsdom.


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

  • Каждый метод компонента должен иметь хотя бы один тест.
  • Отдельные сценарии для событий DOM.
  • Проверка начального состояния и реактивных свойств.
  • Мокирование всех внешних зависимостей.
  • Асинхронные операции тестировать через await или промисы.
  • Использовать beforeEach и afterEach для изоляции тестов и очистки DOM.

Unit-тестирование Lit-компонентов требует сочетания работы с реактивными свойствами, shadow DOM и событийными обработчиками. Комбинация Jest и утилит типа @open-wc/testing или Testing Library обеспечивает удобный, изолированный и воспроизводимый подход к проверке компонентов.