Testing Guidelines
Unit testing patterns for contributors.
Test Organization
Tests are organized to mirror the source structure:
tests/
├── unit/
│ ├── react/ # React layer tests
│ ├── playwright/ # Playwright layer tests
│ └── shared/ # Shared utility tests
├── integration/ # E2E tests with Playwright
├── fixtures/ # Test app and fixtures
└── mocks/ # Mock factoriesRunning Tests
# Unit tests
npm test # Run all unit tests
npm test -- path/to/test # Run specific test file
npm run test:watch # Watch mode
# E2E tests
npm run test:e2e # Run integration tests
# Coverage
npm run test:coverage # Generate coverage reportMock Factories
Use the mock factories in tests/mocks/playwrightMocks.ts:
import {
createMockPage,
createMockCDPSession,
createMockTestInfo,
createMockProfilerState,
createMockPerformance,
} from '../mocks/playwrightMocks';createMockPage()
Creates a mock Playwright Page:
const page = createMockPage({
browserName: 'chromium',
evaluate: vi.fn().mockResolvedValue(mockState),
});createMockCDPSession()
Creates a mock CDP session with event handling:
const cdp = createMockCDPSession();
cdp.send.mockResolvedValue({});createFailingCDPSession()
Creates a CDP session that fails (for error path testing):
const cdp = createFailingCDPSession('Connection refused');createMockProfilerState()
Creates mock profiler state with samples:
const state = createMockProfilerState({
samples: [
{ id: 'component', phase: 'mount', actualDuration: 10 },
{ id: 'component', phase: 'update', actualDuration: 5 },
],
});Test Patterns
Testing CDP Features
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createMockPage, createMockCDPSession } from '../mocks/playwrightMocks';
describe('cpuThrottlingFeature', () => {
let page: ReturnType<typeof createMockPage>;
let cdp: ReturnType<typeof createMockCDPSession>;
beforeEach(() => {
cdp = createMockCDPSession();
page = createMockPage({
browserName: 'chromium',
context: {
newCDPSession: vi.fn().mockResolvedValue(cdp),
},
});
});
it('should apply throttle rate', async () => {
const handle = await cpuThrottlingFeature.start(page, { rate: 4 });
expect(cdp.send).toHaveBeenCalledWith('Emulation.setCPUThrottlingRate', {
rate: 4,
});
expect(handle?.isActive()).toBe(true);
});
it('should skip on non-chromium', async () => {
page = createMockPage({ browserName: 'firefox' });
const handle = await cpuThrottlingFeature.start(page, { rate: 4 });
expect(handle).toBeNull();
});
});Testing Threshold Validation
describe('assertDurationThreshold', () => {
it('should pass when under threshold', () => {
expect(() => {
assertDurationThreshold(100, 500, 20); // 100 < 600 (500 + 20%)
}).not.toThrow();
});
it('should fail when over threshold', () => {
expect(() => {
assertDurationThreshold(700, 500, 20); // 700 > 600
}).toThrow(/Duration.*exceeded/);
});
});Testing React Hooks
import { renderHook } from '@testing-library/react';
import { PerformanceProvider, usePerformanceRequired } from '../../../src/react';
describe('usePerformanceRequired', () => {
it('should return profiler context when inside provider', () => {
const { result } = renderHook(() => usePerformanceRequired(), {
wrapper: PerformanceProvider,
});
expect(result.current.onProfilerRender).toBeDefined();
});
it('should throw when outside provider', () => {
expect(() => {
renderHook(() => usePerformanceRequired());
}).toThrow(/must be used within/);
});
});Coverage Requirements
- Minimum 80% coverage required
- Focus on:
- Edge cases and error paths
- CDP feature graceful degradation
- Threshold calculations with buffers
- Environment-specific behavior (CI vs local)
Best Practices
- Isolate tests – Each test should be independent
- Use factories – Don't create mocks inline
- Test error paths – Ensure graceful degradation
- Descriptive names –
should [expected behavior] when [condition] - Group related tests – Use
describeblocks logically
Related
- Architecture – Internal design
- Custom Fixtures – Extending functionality