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

Тестирование пользовательских компонентов является критическим аспектом при разработке веб-приложений. SkateJS, как библиотека для создания веб-компонентов, предоставляет гибкий подход к построению кастомных элементов, что требует особого внимания к юнит-тестированию и интеграционным тестам.


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

В SkateJS компоненты строятся на базе функций define и withComponent, что позволяет создавать независимые элементы с собственными свойствами, методами и жизненным циклом. Основная задача тестирования — убедиться, что:

  • Компонент корректно рендерится.
  • События и обратные вызовы работают правильно.
  • Состояние компонента изменяется ожидаемо при изменении свойств.
  • Методы жизненного цикла вызываются в правильной последовательности.

Для тестирования чаще всего используют Jest, Mocha или Karma в сочетании с Testing Library для работы с DOM.


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

Для тестирования компонентов SkateJS необходимо:

  1. Установить библиотеки:
npm install --save-dev jest @testing-library/dom @testing-library/jest-dom
  1. Настроить Jest для работы с DOM. SkateJS требует наличие window и document, поэтому в конфигурации Jest нужно указать тестовую среду jsdom:
{
  "testEnvironment": "jsdom"
}
  1. Подключить необходимые утилиты для работы с кастомными элементами:
import { define } from 'skatejs';
import '@testing-library/jest-dom';
import { screen, fireEvent } from '@testing-library/dom';

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

Юнит-тест фокусируется на проверке одного компонента в изоляции. Рассмотрим базовый пример компонента my-button:

import { define, props } from 'skatejs';

class MyButton extends HTMLElement {
  static props = {
    label: props.string
  };

  renderCallback() {
    this.textContent = this.label || 'Click me';
  }
}

define('my-button', MyButton);

Тест для проверки рендеринга и свойства label может выглядеть так:

test('должен рендерить текст из свойства label', () => {
  const button = document.createElement('my-button');
  button.label = 'Нажми меня';
  document.body.appendChild(button);

  expect(button).toHaveTextContent('Нажми меня');
});

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

  • Создание элемента через document.createElement позволяет полностью контролировать его свойства и DOM.
  • appendChild в document.body необходим для вызова методов жизненного цикла SkateJS, таких как connectedCallback.
  • @testing-library/jest-dom предоставляет удобные матчеры для проверки DOM-содержимого.

Тестирование событий

События играют важную роль в интерактивных компонентах. SkateJS позволяет использовать стандартный API DOM для генерации и прослушивания событий:

test('должен вызывать событие click', () => {
  const button = document.createElement('my-button');
  document.body.appendChild(button);

  const handleClick = jest.fn();
  button.addEventListener('click', handleClick);

  button.click();

  expect(handleClick).toHaveBeenCalledTimes(1);
});

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

  • Использование стандартного метода click() гарантирует, что событие проходит через все обработчики компонента.
  • jest.fn() позволяет легко отслеживать вызовы функций-обработчиков.

Проверка жизненного цикла

SkateJS предоставляет несколько методов жизненного цикла: createdCallback, connectedCallback, disconnectedCallback, а также renderCallback. Тестирование их последовательности помогает выявить ошибки инициализации или очистки ресурсов:

class LifecycleComponent extends HTMLElement {
  connectedCalled = false;
  disconnectedCalled = false;

  connectedCallback() {
    this.connectedCalled = true;
  }

  disconnectedCallback() {
    this.disconnectedCalled = true;
  }
}

define('lifecycle-component', LifecycleComponent);

test('должны вызываться connectedCallback и disconnectedCallback', () => {
  const comp = document.createElement('lifecycle-component');
  document.body.appendChild(comp);

  expect(comp.connectedCalled).toBe(true);

  document.body.removeChild(comp);
  expect(comp.disconnectedCalled).toBe(true);
});

Тестирование свойств и реактивности

SkateJS позволяет автоматически отслеживать изменения свойств через props и триггерить renderCallback. Тестирование реактивного поведения важно для компонентов с динамическим контентом:

test('должен обновлять текст при изменении свойства', () => {
  const button = document.createElement('my-button');
  document.body.appendChild(button);

  button.label = 'Сначала';
  expect(button).toHaveTextContent('Сначала');

  button.label = 'Потом';
  expect(button).toHaveTextContent('Потом');
});

В этом примере проверяется, что компонент корректно реагирует на изменения свойства и обновляет DOM без дополнительного вмешательства.


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

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

const parentHtml = `
  <div>
    <my-button label="Кнопка 1"></my-button>
    <my-button label="Кнопка 2"></my-button>
  </div>
`;

document.body.innerHTML = parentHtml;

test('должны корректно отображаться все дочерние компоненты', () => {
  const buttons = document.querySelectorAll('my-button');
  expect(buttons.length).toBe(2);
  expect(buttons[0]).toHaveTextContent('Кнопка 1');
  expect(buttons[1]).toHaveTextContent('Кнопка 2');
});

Особенности интеграционных тестов:

  • Позволяют проверить совместимость компонентов.
  • Помогают выявлять ошибки при вложенных структурах.
  • Обеспечивают уверенность, что свойства и события корректно передаются между компонентами.

Мокинг и изоляция зависимостей

Для сложных компонентов с внешними API или сервисами рекомендуется использовать моки. SkateJS позволяет создавать полностью изолированные экземпляры компонентов, что облегчает подмену зависимостей:

const fetchDataMock = jest.fn().mockResolvedValue('данные');

class DataComponent extends HTMLElement {
  data = '';

  async connectedCallback() {
    this.data = await fetchDataMock();
    this.textContent = this.data;
  }
}

define('data-component', DataComponent);

test('должен рендерить данные после fetch', async () => {
  const comp = document.createElement('data-component');
  document.body.appendChild(comp);

  await new Promise(setImmediate);

  expect(comp).toHaveTextContent('данные');
  expect(fetchDataMock).toHaveBeenCalled();
});

Практические рекомендации

  • Создавать отдельные тесты для каждого свойства и события.
  • Использовать @testing-library/dom для более читаемых и надежных проверок DOM.
  • Всегда очищать DOM после тестов, чтобы избежать побочных эффектов.
  • Проверять жизненный цикл компонентов и реактивность, особенно при изменении свойств.

Тщательное тестирование компонентов SkateJS обеспечивает стабильность и предсказуемость пользовательских элементов, предотвращает регрессии при обновлении библиотеки и ускоряет разработку сложных веб-приложений.