Unit тестирование с Jest и Testing Library

Unit-тестирование в Svelte предоставляет возможность изолированного тестирования компонентов, что позволяет улучшить качество и стабильность кода. Важным инструментом для этого являются Jest и Testing Library — два популярных инструмента для тестирования в экосистеме JavaScript. Jest предоставляет тестовый фреймворк, а Testing Library ориентирована на тестирование пользовательских интерфейсов. Эти инструменты помогают не только проверять функциональность компонентов, но и соблюдать принципы доступности и удобства использования.

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

Для начала необходимо установить все необходимые зависимости для тестирования компонентов Svelte с использованием Jest и Testing Library. Важно, чтобы среда была правильно настроена, чтобы тесты могли работать без ошибок.

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

    npm install --save-dev jest @testing-library/svelte @testing-library/jest-dom @babel/preset-env svelte-jester

    Здесь:

    • jest — сам фреймворк для тестирования.
    • @testing-library/svelte — библиотека для тестирования компонентов Svelte.
    • @testing-library/jest-dom — набор полезных матчеров для работы с DOM.
    • svelte-jester — преобразователь для поддержки Svelte в Jest.
  2. Настройка конфигурации Jest. Для этого нужно создать или отредактировать файл jest.config.js:

    module.exports = {
      transform: {
        '^.+\\.svelte$': 'svelte-jester',
        '^.+\\.js$': 'babel-jest',
      },
      moduleFileExtensions: ['js', 'svelte'],
    };

    Этот файл указывает Jest, как обрабатывать файлы Svelte и JavaScript, а также настраивает необходимые транспайлера.

  3. Добавление конфигурации Babel в файл .babelrc для использования современных возможностей Jav * aScript:

    {
      "presets": ["@babel/preset-env"]
    }

Создание простого компонента Svelte для тестирования

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

<!-- Greeting.svelte -->
<script>
  export let name = 'Мир';
</script>

<p>Привет, {name}!</p>

Написание теста для компонента

Теперь, когда компонент готов, можно переходить к написанию теста. Важно, чтобы тесты были направлены на функциональность компонента, а не на его внутреннюю структуру или имплементацию.

// Greeting.test.js
import { render } from '@testing-library/svelte';
import Greeting from './Greeting.svelte';

test('отображает приветствие с переданным именем', () => {
  const { getByText } = render(Greeting, { name: 'Алексей' });

  // Проверяем, что на странице появился текст "Привет, Алексей!"
  expect(getByText('Привет, Алексей!')).toBeInTheDocument();
});

test('по умолчанию отображает приветствие с именем "Мир"', () => {
  const { getByText } = render(Greeting);

  // Проверяем, что на странице появился текст "Привет, Мир!"
  expect(getByText('Привет, Мир!')).toBeInTheDocument();
});

В этом примере используются следующие моменты:

  • render() — функция из Testing Library, которая рендерит компонент Svelte.
  • getByText() — метод, позволяющий найти элемент по тексту внутри него.
  • expect().toBeInTheDocument() — матчеры, предоставляемые jest-dom, которые проверяют наличие элемента в DOM.

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

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

Пример компонента с кнопкой:

<!-- Counter.svelte -->
<script>
  let count = 0;

  function increment() {
    count += 1;
  }
</script>

<button on:click={increment}>Увеличить</button>
<p>Счёт: {count}</p>

Тестирование этого компонента с проверкой реакции на клики:

// Counter.test.js
import { render, fireEvent } from '@testing-library/svelte';
import Counter from './Counter.svelte';

test('увеличивает счёт при клике на кнопку', async () => {
  const { getByText } = render(Counter);

  const button = getByText('Увеличить');
  const counter = getByText('Счёт: 0');

  // Проверяем, что начальное значение счётчика — 0
  expect(counter).toHaveTextContent('Счёт: 0');

  // Симулируем клик по кнопке
  await fireEvent.click(button);

  // Проверяем, что счёт увеличился на 1
  expect(counter).toHaveTextContent('Счёт: 1');
});

Здесь:

  • fireEvent.click() используется для симуляции клика по кнопке.
  • toHaveTextContent() — матчеры для проверки текста, который отображается в элементе.

Проверка асинхронных операций

Svelte компоненты могут работать с асинхронными операциями, например, с API-запросами. В таких случаях важно правильно обрабатывать асинхронность в тестах. Jest предоставляет механизм для работы с промисами и асинхронными функциями через async/await.

Пример компонента с асинхронным запросом:

<!-- FetchData.svelte -->
<script>
  import { onMount } from 'svelte';

  let data = null;
  let error = null;

  onMount(async () => {
    try {
      const response = await fetch('https://api.example.com/data');
      data = await response.json();
    } catch (err) {
      error = err.message;
    }
  });
</script>

{#if error}
  <p>Ошибка: {error}</p>
{:else if data}
  <p>Данные: {JSON.stringify(data)}</p>
{:else}
  <p>Загрузка...</p>
{/if}

Тестирование этого компонента с использованием jest:

// FetchData.test.js
import { render, waitFor } from '@testing-library/svelte';
import FetchData from './FetchData.svelte';

test('показывает данные после успешного запроса', async () => {
  // Мокаем ответ API
  global.fetch = jest.fn().mockResolvedValue({
    json: jest.fn().mockResolvedValue({ name: 'Сергей', age: 30 }),
  });

  const { getByText } = render(FetchData);

  // Проверяем, что компонент отображает текст "Загрузка..."
  expect(getByText('Загрузка...')).toBeInTheDocument();

  // Ожидаем, что данные будут загружены
  await waitFor(() => getByText('Данные: {"name":"Сергей","age":30}'));

  // Проверяем, что данные отобразились правильно
  expect(getByText('Данные: {"name":"Сергей","age":30}')).toBeInTheDocument();
});

test('показывает ошибку при неудачном запросе', async () => {
  // Мокаем ошибку API
  global.fetch = jest.fn().mockRejectedValue(new Error('Сетевая ошибка'));

  const { getByText } = render(FetchData);

  // Ожидаем, что ошибка будет отображена
  await waitFor(() => getByText('Ошибка: Сетевая ошибка'));

  // Проверяем, что ошибка отобразилась правильно
  expect(getByText('Ошибка: Сетевая ошибка')).toBeInTheDocument();
});

В этом примере:

  • jest.fn().mockResolvedValue() и jest.fn().mockRejectedValue() используются для мокирования успешных и неудачных ответов от API.
  • waitFor() — помогает ожидать изменений в DOM после асинхронных операций.

Проверка доступности

Одной из важных задач при тестировании компонентов является проверка их доступности (accessibility). Использование библиотек вроде @testing-library/jest-dom позволяет легко проверять, что элементы компонента доступны для пользователей с ограниченными возможностями.

Пример проверки доступности кнопки:

// AccessibleButton.test.js
import { render } from '@testing-library/svelte';
import AccessibleButton from './AccessibleButton.svelte';

test('кнопка должна быть доступна для пользователя с экрана', () => {
  const { getByRole } = render(AccessibleButton);
  
  // Проверяем, что кнопка имеет роль "button" и доступна
  expect(getByRole('button')).toBeInTheDocument();
  expect(getByRole('button')).toHaveAccessibleName('Нажми меня');
});

Здесь:

  • getByRole('button') используется для нахождения кнопки по роли.
  • toHaveAccessibleName() проверяет, что кнопка имеет доступное имя, которое будет озвучено пользователю с использованием экрана.