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

В Svelte концепция реактивности является одной из основ, на которой строится весь фреймворк. Важным элементом, влияющим на реактивность, являются stores — абстракции, которые позволяют управлять состоянием в приложении и обеспечивают обмен данными между компонентами. Тестирование реактивности и работы с store’ами требует особого подхода, поскольку важна не только правильная логика приложения, но и корректное обновление интерфейса при изменении данных. В данной главе рассмотрим, как тестировать работу с реактивностью и stores в Svelte.

Основы работы с stores в Svelte

В Svelte stores — это объект, который содержит данные и предоставляет методы для их изменения и наблюдения за этими изменениями. В стандартном Svelte можно использовать три типа stores:

  1. Writable — изменяемые данные, которые можно как читать, так и изменять.
  2. Readable — только для чтения, используется для данных, которые не изменяются внутри компонента.
  3. Derived — создает новый store на основе другого или нескольких других stores.

Для создания writable store используется функция writable:

import { writable } from 'svelte/store';

const count = writable(0);

Для работы с данными можно подписываться на store, используя метод $ в компоненте:

<script>
  import { count } from './store.js';
</script>

<p>Счёт: {$count}</p>

Тестирование реакции на изменения в stores

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

Использование Svelte Testing Library

Для тестирования компонентов с использованием Svelte можно воспользоваться Svelte Testing Library. Она предоставляет удобный API для проверки интерфейса, в том числе для тестирования реактивных изменений.

Основная задача при тестировании — убедиться, что интерфейс корректно обновляется после изменения состояния. В следующем примере показано, как можно протестировать компонент с использованием writable store.

// Счётчик.svelte
<script>
  import { writable } from 'svelte/store';
  export let count = writable(0);
</script>

<button on:click={() => $count += 1}>Увеличить</button>
<p>Счёт: {$count}</p>

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

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

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

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

  await fireEvent.click(button);
  expect(paragraph).toHaveTextContent('Счёт: 1');

  await fireEvent.click(button);
  expect(paragraph).toHaveTextContent('Счёт: 2');
});

В данном примере создаётся тест, который проверяет, что при каждом нажатии на кнопку счёт увеличивается на единицу, и интерфейс обновляется соответствующим образом.

Работа с derived store

Тестирование derived store также важно, поскольку оно обеспечивает реактивность на основе других stores. Например, если необходимо вычислить значение, которое зависит от других данных, используется derived store.

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

// App.svelte
<script>
  import { writable, derived } from 'svelte/store';

  const a = writable(2);
  const b = writable(3);

  const sum = derived([a, b], ([$a, $b]) => $a + $b);
</script>

<p>Сумма: {$sum}</p>
<button on:click={() => $a += 1}>Увеличить A</button>
<button on:click={() => $b += 1}>Увеличить B</button>

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

import { render, fireEvent } from '@testing-library/svelte';
import App from './App.svelte';
import { writable } from 'svelte/store';

test('обновляет сумму при изменении A и B', async () => {
  const { getByText } = render(App);

  const sumText = getByText('Сумма: 5');
  const buttonA = getByText('Увеличить A');
  const buttonB = getByText('Увеличить B');

  await fireEvent.click(buttonA);
  expect(sumText).toHaveTextContent('Сумма: 6');

  await fireEvent.click(buttonB);
  expect(sumText).toHaveTextContent('Сумма: 7');
});

В данном случае тест проверяет, что сумма обновляется корректно при изменении значений a и b.

Тестирование реакции на асинхронные изменения в store

Часто в приложениях возникает необходимость работать с асинхронными операциями, например, загрузка данных с сервера. Для тестирования таких операций можно использовать async и await в тестах. В Svelte store можно также асинхронно обновлять данные.

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

<script>
  import { writable } from 'svelte/store';

  const data = writable([]);

  async function fetchData() {
    const response = await fetch('/api/data');
    const result = await response.json();
    data.set(result);
  }

  fetchData();
</script>

<ul>
  {#each $data as item}
    <li>{item.name}</li>
  {/each}
</ul>

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

import { render, screen, waitFor } from '@testing-library/svelte';
import App from './App.svelte';

test('загружает данные и отображает их', async () => {
  const mockData = [{ name: 'Item 1' }, { name: 'Item 2' }];
  global.fetch = jest.fn().mockResolvedValue({
    json: jest.fn().mockResolvedValue(mockData)
  });

  render(App);

  await waitFor(() => expect(screen.getByText('Item 1')).toBeInTheDocument());
  expect(screen.getByText('Item 2')).toBeInTheDocument();
});

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

Мокирование store для тестирования

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

Пример мокирования writable store:

import { writable } from 'svelte/store';

jest.mock('svelte/store', () => ({
  writable: jest.fn(() => ({
    subscribe: jest.fn(callback => callback(5)),
    set: jest.fn(),
    update: jest.fn()
  }))
}));

import Counter from './Counter.svelte';
import { render } from '@testing-library/svelte';

test('проверка работы с мокированным store', () => {
  const { getByText } = render(Counter);
  expect(getByText('Счёт: 5')).toBeInTheDocument();
});

Здесь используется мок для store, чтобы гарантировать возвращение фиксированного значения и протестировать его взаимодействие с компонентом.

Заключение

Тестирование реактивных данных и store в Svelte требует внимательности к деталям, поскольку оно связано не только с правильной логикой, но и с тем, как данные влияют на обновление интерфейса. Важно использовать соответствующие инструменты, такие как Svelte Testing Library, для проверки корректности работы с состоянием, и быть готовым к тестированию как синхронных, так и асинхронных операций. Тщательное тестирование помогает обеспечить стабильность приложения и правильность его работы в различных сценариях.