Testing Guide
Testing Guide
Section titled “Testing Guide”Guidelines for writing unit and end-to-end tests in IONFLOW Frontend.
Overview
Section titled “Overview”| Type | Tool | Location | Coverage |
|---|---|---|---|
| Unit Tests | Vitest | *.spec.ts | Components, composables, services |
| E2E Tests | Playwright | e2e/ | User flows |
Unit Testing with Vitest
Section titled “Unit Testing with Vitest”Running Tests
Section titled “Running Tests”# Run all unit testspnpm test:unit
# Run in watch modepnpm test:unit --watch
# Run with coveragepnpm test:unit --coverage
# Run specific filepnpm test:unit src/components/common/TruncateText.spec.ts
# Run tests matching patternpnpm test:unit --grep "TruncateText"Test File Location
Section titled “Test File Location”Test files should be next to the component:
src/components/common/├── TruncateText.vue└── TruncateText.spec.tsBasic Test Structure
Section titled “Basic Test Structure”import { describe, it, expect, beforeEach, vi } from 'vitest';import { mount } from '@vue/test-utils';import ComponentName from './ComponentName.vue';
describe('ComponentName', () => { beforeEach(() => { // Setup before each test });
it('should render correctly', () => { const wrapper = mount(ComponentName); expect(wrapper.exists()).toBe(true); });
it('should display props', () => { const wrapper = mount(ComponentName, { props: { title: 'Test Title', }, }); expect(wrapper.text()).toContain('Test Title'); });});Testing Components
Section titled “Testing Components”Props Testing
Section titled “Props Testing”it('should accept and display props', () => { const wrapper = mount(TruncateText, { props: { text: 'Hello World', maxLength: 5, }, }); expect(wrapper.text()).toContain('Hello...');});Events Testing
Section titled “Events Testing”it('should emit events', async () => { const wrapper = mount(ComponentName);
await wrapper.find('button').trigger('click');
expect(wrapper.emitted('select')).toBeTruthy(); expect(wrapper.emitted('select')?.[0]).toEqual([expectedPayload]);});Slots Testing
Section titled “Slots Testing”it('should render slots', () => { const wrapper = mount(ComponentName, { slots: { default: '<p>Slot content</p>', header: '<h1>Header</h1>', }, });
expect(wrapper.html()).toContain('Slot content'); expect(wrapper.html()).toContain('Header');});Testing Composables
Section titled “Testing Composables”import { describe, it, expect } from 'vitest';import { useDataTableParams } from './data.table.params';
describe('useDataTableParams', () => { it('should initialize with default values', () => { const { first, rows } = useDataTableParams();
expect(first.value).toBe(0); expect(rows.value).toBe(10); });
it('should update on page change', () => { const { first, rows, onPage } = useDataTableParams();
onPage({ first: 20, rows: 10 });
expect(first.value).toBe(20); });});Testing Services
Section titled “Testing Services”import { describe, it, expect, vi, beforeEach } from 'vitest';import axios from 'axios';import flowService from './flow.service';
vi.mock('axios');
describe('FlowService', () => { beforeEach(() => { vi.clearAllMocks(); });
it('should fetch flows', async () => { const mockData = { data: [{ id: '1', name: 'Flow 1' }] }; vi.mocked(axios.get).mockResolvedValue({ data: mockData });
const result = await flowService.list();
expect(result.data).toEqual(mockData); });});Testing Pinia Stores
Section titled “Testing Pinia Stores”import { describe, it, expect, beforeEach } from 'vitest';import { setActivePinia, createPinia } from 'pinia';import { useAuthStore } from './auth';
describe('useAuthStore', () => { beforeEach(() => { setActivePinia(createPinia()); });
it('should initialize with null user', () => { const store = useAuthStore(); expect(store.user).toBeNull(); });
it('should set user', () => { const store = useAuthStore(); const user = { id: 1, name: 'Test User' };
store.setUser(user);
expect(store.user).toEqual(user); });});Mocking
Section titled “Mocking”Mocking Modules
Section titled “Mocking Modules”vi.mock('@/services/tenant/flow.service', () => ({ default: { list: vi.fn().mockResolvedValue({ data: [] }), get: vi.fn(), },}));Mocking Composables
Section titled “Mocking Composables”vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key, }),}));Mocking Router
Section titled “Mocking Router”import { mount } from '@vue/test-utils';import { createRouter, createMemoryHistory } from 'vue-router';
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/', component: {} }],});
const wrapper = mount(Component, { global: { plugins: [router], },});E2E Testing with Playwright
Section titled “E2E Testing with Playwright”Running Tests
Section titled “Running Tests”# Run all E2E testspnpm test:e2e
# Run with UIpnpm test:e2e --ui
# Run specific testpnpm test:e2e e2e/login.spec.ts
# Run in headed modepnpm test:e2e --headedTest Structure
Section titled “Test Structure”import { test, expect } from '@playwright/test';
test.describe('Login', () => { test.beforeEach(async ({ page }) => { await page.goto('/login'); });
test('should display login form', async ({ page }) => { await expect(page.getByRole('heading', { name: 'Login' })).toBeVisible(); await expect(page.getByLabel('Email')).toBeVisible(); await expect(page.getByLabel('Password')).toBeVisible(); });
test('should login successfully', async ({ page }) => { await page.getByLabel('Password').fill('password'); await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL('/dashboard'); });});Page Objects
Section titled “Page Objects”import type { Page } from '@playwright/test';
export class LoginPage { constructor(private page: Page) {}
async goto() { await this.page.goto('/login'); }
async login(email: string, password: string) { await this.page.getByLabel('Email').fill(email); await this.page.getByLabel('Password').fill(password); await this.page.getByRole('button', { name: 'Login' }).click(); }}
// Usagetest('should login', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto();});Test Coverage
Section titled “Test Coverage”Required Coverage
Section titled “Required Coverage”| Component Type | Coverage |
|---|---|
| Common components | Required |
| Feature components | Recommended |
| Services | Required |
| Stores | Required |
| Composables | Required |
Viewing Coverage
Section titled “Viewing Coverage”# Generate coverage reportpnpm test:unit --coverage
# Open HTML reportopen coverage/index.htmlBest Practices
Section titled “Best Practices”Do’s ✅
Section titled “Do’s ✅”- Test behavior, not implementation
- Use meaningful test descriptions
- Keep tests independent
- Mock external dependencies
- Test edge cases
Don’ts ❌
Section titled “Don’ts ❌”- Don’t test third-party libraries
- Don’t test implementation details
- Don’t write flaky tests
- Don’t skip error scenarios
- Don’t couple tests