17. Тестирование
Qwik City рекомендует использовать Vitest для unit-тестов и тестирования компонентов, и Playwright для E2E тестирования.
Настройка Vitest
Заголовок раздела «Настройка Vitest»npm run qwik add vitestimport { defineConfig } from 'vitest/config';import { qwikVite } from '@builder.io/qwik/optimizer';
export default defineConfig({ plugins: [qwikVite()], test: { globals: true, environment: 'node', },});Unit тестирование функций
Заголовок раздела «Unit тестирование функций»export function formatPrice(price: number, currency = 'RUB'): string { return new Intl.NumberFormat('ru-RU', { style: 'currency', currency, }).format(price);}
// src/utils/format.test.tsimport { describe, it, expect } from 'vitest';import { formatPrice } from './format';
describe('formatPrice', () => { it('форматирует рублёвую цену', () => { expect(formatPrice(1000)).toBe('1 000 ₽'); });
it('форматирует цену в долларах', () => { expect(formatPrice(99.99, 'USD')).toContain('99.99'); });
it('обрабатывает ноль', () => { expect(formatPrice(0)).toBe('0 ₽'); });});Тестирование компонентов
Заголовок раздела «Тестирование компонентов»Qwik предоставляет createDOM для тестирования компонентов без браузера:
import { createDOM } from '@builder.io/qwik/testing';import { describe, it, expect } from 'vitest';import { Counter } from './counter';
describe('Counter', () => { it('рендерит начальное значение', async () => { const { screen, render } = await createDOM();
await render(<Counter initialValue={5} />);
expect(screen.querySelector('[data-test="count"]')?.textContent).toBe('5'); });
it('увеличивает счётчик при клике', async () => { const { screen, render, userEvent } = await createDOM();
await render(<Counter initialValue={0} />);
const button = screen.querySelector('[data-test="increment"]')!; await userEvent(button, 'click');
expect(screen.querySelector('[data-test="count"]')?.textContent).toBe('1'); });});Тестирование routeLoader$
Заголовок раздела «Тестирование routeLoader$»import { describe, it, expect, vi } from 'vitest';import { createRequestEvent } from '@builder.io/qwik-city/testing';
// Мокаем БДvi.mock('../../../lib/db', () => ({ db: { posts: { findUnique: vi.fn(), }, },}));
describe('usePost loader', () => { it('возвращает пост по slug', async () => { const mockPost = { id: '1', title: 'Test', slug: 'test' }; vi.mocked(db.posts.findUnique).mockResolvedValue(mockPost);
const event = createRequestEvent({ params: { slug: 'test' } }); const result = await usePost.call(event);
expect(result).toEqual(mockPost); });
it('возвращает 404 если пост не найден', async () => { vi.mocked(db.posts.findUnique).mockResolvedValue(null);
const event = createRequestEvent({ params: { slug: 'nonexistent' } });
await expect(usePost.call(event)).rejects.toThrow(); });});E2E тестирование с Playwright
Заголовок раздела «E2E тестирование с Playwright»npm run qwik add playwrightimport { test, expect } from '@playwright/test';
test('главная страница загружается', async ({ page }) => { await page.goto('/');
await expect(page).toHaveTitle(/Мой Qwik App/); await expect(page.locator('h1')).toBeVisible();});
test('счётчик работает', async ({ page }) => { await page.goto('/counter');
const count = page.locator('[data-test="count"]'); const increment = page.locator('[data-test="increment"]');
await expect(count).toHaveText('0'); await increment.click(); await expect(count).toHaveText('1');});
test('форма отправляется', async ({ page }) => { await page.goto('/contact');
await page.fill('[name="message"]', 'Привет!'); await page.click('[type="submit"]');
await expect(page.locator('.success-message')).toBeVisible();});