Тестирование composables и Pinia-сторов
↑ 5.7 Тестирование приложения · → Следующая
Зачем это на собесе
Как тестировать логику вне компонентов: composables с хуками и сторы Pinia.
Объяснение
Composables
Composable без хуков жизненного цикла — обычная функция:
it("useCounter увеличивает значение", () => {
const { count, inc } = useCounter(1);
inc();
expect(count.value).toBe(2);
});Composable, использующий onMounted, provide/inject, useRoute, требует контекста компонента — хелпер withSetup:
import { createApp } from "vue";
export function withSetup<T>(composable: () => T, { plugins = [] as any[] } = {}): [T, App] {
let result!: T;
const app = createApp({ setup() { result = composable(); return () => null; } });
plugins.forEach(p => app.use(p));
app.mount(document.createElement("div"));
return [result, app];
}
it("useEventListener снимает слушатель при unmount", () => {
const spy = vi.fn();
const [, app] = withSetup(() => useEventListener(window, "resize", spy));
window.dispatchEvent(new Event("resize")); expect(spy).toHaveBeenCalledTimes(1);
app.unmount();
window.dispatchEvent(new Event("resize")); expect(spy).toHaveBeenCalledTimes(1);
});Асинхронные и с таймерами — vi.useFakeTimers(), await nextTick(), flushPromises(), vi.waitFor.
Pinia
import { setActivePinia, createPinia } from "pinia";
beforeEach(() => setActivePinia(createPinia())); // чистый стор на каждый тест
it("cart: добавляет и считает сумму", () => {
const cart = useCartStore();
cart.add({ id: 1, price: 100, qty: 2 });
expect(cart.total).toBe(200);
});
it("auth.login сохраняет пользователя", async () => {
vi.mocked(api.login).mockResolvedValue({ user: { id: 1 }, token: "t" });
const auth = useAuthStore();
await auth.login("a@x.ru", "pwd");
expect(auth.isAuthenticated).toBe(true);
});Для компонентов: createTestingPinia({ createSpy: vi.fn, initialState: { auth: { user } }, stubActions: false }) — подмена состояния и слежение за actions.
const wrapper = mount(Comp, { global: { plugins: [createTestingPinia({ createSpy: vi.fn })] } });
const store = useCartStore(); store.add = vi.fn(); // actions по умолчанию заглушены
await wrapper.get("button").trigger("click"); expect(store.add).toHaveBeenCalledWith(...)Что тестировать: логику actions/getters, обработку ошибок, побочные эффекты (сохранение, вызовы API — замоканы), reset. Не тестируйте реализацию Pinia.
Нюансы и подводные камни
- Без нового
createPinia()между тестами состояние протекает. - Composable с
injectтребуетprovideпри монтировании. - Забытая очистка (
app.unmount()) оставляет слушатели. createTestingPiniaпо умолчанию заглушает actions: включайтеstubActions: false, если нужна реальная логика.- Сторы, зависящие от
localStorage, требуют очистки между тестами.
Практика
- Напишите
withSetupи протестируйте composable сonMounted. - Протестируйте стор корзины: getters, actions, ошибки.
- Проверьте, что компонент вызывает action при клике (
createTestingPinia).
Вопросы с ответами
Как протестировать composable с хуками жизненного цикла?
Смонтировать его в тестовом приложении/компоненте (хелпер
withSetup) и, при необходимости, размонтировать.
Чем
createTestingPiniaполезен?Позволяет задать начальное состояние и следить за вызовами actions без реальных побочных эффектов.