Перейти к содержимому

Svelte ​

vitest-auto-spy/svelte превращает сервис или стор, написанный классом, в типизированный спай. Спай — объект-заглушка: каждый его метод запоминает вызовы и возвращает то, что вы задали. Спай передаётся компоненту так же, как настоящий объект: через пропсы, контекст или модуль, который компонент импортирует.

Через пропсы ​

ts
import { fireEvent, render, screen } from '@testing-library/svelte';
import { expect, it } from 'vitest';
import { createSpyFromClass } from 'vitest-auto-spy/svelte';

import Cart from './Cart.svelte';
import { CartStore } from './cart-store';

it('renders the total the store reports', () => {
  const cartStore = createSpyFromClass(CartStore);

  cartStore.total.mockReturnValue(42);

  render(Cart, { props: { store: cartStore } });

  expect(screen.getByText('$42')).toBeInTheDocument();
  expect(cartStore.total).toHaveBeenCalled();
});

it('checks out when the button is clicked', async () => {
  const cartStore = createSpyFromClass(CartStore);

  cartStore.checkout.resolveWith({ orderId: 'ord_42' }); // checkout() возвращает Promise

  render(Cart, { props: { store: cartStore } });
  await fireEvent.click(screen.getByRole('button', { name: 'Check out' }));

  expect(cartStore.checkout).toHaveBeenCalledTimes(1);
});

Ответы задаются через mockReturnValue (обычное значение) или resolveWith (успешный промис), вызовы проверяются обычными матчерами toHaveBeenCalled…. Для toBeInTheDocument добавьте import '@testing-library/jest-dom/vitest' в setup-файл Vitest.

vitest-auto-spy/svelte — точка входа, то есть путь импорта для Svelte-спек. Она экспортирует API ядра библиотеки: createSpyFromClass, createAutoMock, тип Spy<T>, asInstance, resetAutoSpy и остальное. В Svelte нет внедрения зависимостей на классах, поэтому дополнительных хелперов нет.

Через контекст ​

Если компонент берёт стор через getContext, передайте спай под тем же ключом:

ts
import { render, screen } from '@testing-library/svelte';
import { expect, it } from 'vitest';
import { createSpyFromClass } from 'vitest-auto-spy/svelte';

import Cart from './Cart.svelte';
import { CART_STORE, CartStore } from './cart-store';

it('renders the total the store reports', () => {
  const cartStore = createSpyFromClass(CartStore);

  cartStore.total.mockReturnValue(42);

  render(Cart, { context: new Map([[CART_STORE, cartStore]]) });

  expect(screen.getByText('$42')).toBeInTheDocument();
});

Через замоканный модуль ​

Если компонент импортирует готовый экземпляр (синглтон) из модуля, замените этот экспорт спаем:

ts
import { render } from '@testing-library/svelte';
import { expect, it, vi } from 'vitest';
import { createSpyFromClass } from 'vitest-auto-spy/svelte';

import { CartStore } from './cart-store';

const cartStore = vi.hoisted(() => createSpyFromClass(CartStore));

vi.mock('./cart-store', async (importOriginal) => ({
  ...(await importOriginal<typeof import('./cart-store')>()),
  cartStore,
}));

it('checks out through the module singleton', async () => {
  cartStore.checkout.resolveWith({ orderId: 'ord_42' });

  render(await import('./Cart.svelte').then((m) => m.default));

  expect(cartStore.checkout).toHaveBeenCalled();
});

Частая ошибка: спай создан в обычной const в начале файла. Vitest поднимает vi.mock выше всего остального кода, и фабрика эту переменную не видит. Оберните создание в vi.hoisted, как выше.

Какой раннер ​

Точка входа не импортирует ни svelte, ни @testing-library/svelte. Зато она импортирует vitest, поэтому под bun test и node --test не загрузится. На этих раннерах импортируйте createSpyFromClass из точки входа своего раннера (vitest-auto-spy/bun, vitest-auto-spy/node).

Если setup-файл уже импортирует точку входа раннера, например vitest-auto-spy/bun, после импорта этой точки входа остаются моки того раннера.

Подробнее ​

Когда vi.mock нечего заменять ​

vi.mock подменяет модуль по его пути. Некоторые тестовые сборки собирают спеку вместе с импортами в один файл (например, билдер @angular/build:unit-test), и относительный путь больше не указывает на отдельный модуль. Тогда vi.mock ничего не меняет, и компонент работает с настоящим стором. Передавайте спай через пропсы или контекст.

Опубликовано под лицензией MIT.