Test Setup & Configuration
Vitest Configuration
File: vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/__tests__/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'src/__tests__/'
],
lines: 80,
functions: 80,
branches: 75,
statements: 80
},
include: ['src/**/*.test.{ts,tsx}'],
exclude: ['node_modules', 'dist']
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
}
});Global Test Setup
File:
src/__tests__/setup.ts
import { afterAll, afterEach, beforeAll } from 'vitest';
import { cleanup } from '@testing-library/react';
import { server } from './mocks/server';
// Start mock server before all tests
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
// Clean up after each test
afterEach(() => {
cleanup();
server.resetHandlers();
localStorage.clear();
sessionStorage.clear();
vi.clearAllMocks();
});
// Stop server after all tests
afterAll(() => server.close());
// Mock window.matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn()
}))
});
// Mock IntersectionObserver
global.IntersectionObserver = class IntersectionObserver {
constructor() {}
disconnect() {}
observe() {}
takeRecords() {
return [];
}
unobserve() {}
};MSW Configuration
File:
src/__tests__/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);File:
src/__tests__/mocks/handlers.ts
import { rest } from 'msw';
import { mockProducts, mockAuthToken } from './data';
export const handlers = [
// Auth
rest.post('/api/auth/login', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
token: mockAuthToken,
userId: 'user-123',
role: 'admin'
})
);
}),
// Products
rest.get('/api/products', (req, res, ctx) => {
const page = req.url.searchParams.get('page') || '1';
const limit = req.url.searchParams.get('limit') || '20';
return res(ctx.status(200), ctx.json(mockProducts));
}),
rest.post('/api/products', (req, res, ctx) => {
const newProduct = { id: 'prod-123', ...req.body };
return res(ctx.status(201), ctx.json(newProduct));
}),
rest.get('/api/products/:id', (req, res, ctx) => {
const product = mockProducts.find(p => p.id === req.params.id);
return res(
ctx.status(product ? 200 : 404),
ctx.json(product || { error: 'Not found' })
);
}),
rest.put('/api/products/:id', (req, res, ctx) => {
const updated = { ...mockProducts[0], ...req.body };
return res(ctx.status(200), ctx.json(updated));
}),
rest.delete('/api/products/:id', (req, res, ctx) => {
return res(ctx.status(204));
})
];File:
src/__tests__/mocks/data.ts
export const mockAuthToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTEyMyIsInJvbGUiOiJhZG1pbiJ9.signature';
export const mockProducts = [
{
id: 'prod-1',
name: 'Laptop',
quantity: 10,
category: 'Electronics',
sku: 'LAP-001',
price: 999.99,
description: 'High-performance laptop'
},
{
id: 'prod-2',
name: 'Mouse',
quantity: 50,
category: 'Accessories',
sku: 'MOU-001',
price: 29.99,
description: 'Wireless mouse'
}
];
export const createMockProduct = (overrides = {}) => ({
id: `prod-${Math.random()}`,
name: 'Test Product',
quantity: 10,
category: 'Test',
sku: `TEST-${Date.now()}`,
price: 19.99,
description: 'Test description',
...overrides
});Package.json Test Scripts
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest watch",
"test:coverage": "vitest run --coverage",
"test:ui": "vitest --ui",
"test:debug": "vitest --inspect-brk --inspect --single-thread",
"test:unit": "vitest run --grep Unit",
"test:component": "vitest run --grep Component",
"test:integration": "vitest run --grep Integration"
},
"devDependencies": {
"vitest": "^latest",
"@testing-library/react": "^14",
"@testing-library/jest-dom": "^6",
"@testing-library/user-event": "^14",
"jsdom": "^latest",
"msw": "^latest",
"@vitest/ui": "^latest",
"@vitest/coverage-v8": "^latest"
}
}Testing Utilities
File:
src/__tests__/utils/testHelpers.ts
import { ReactElement } from 'react';
import { render, RenderOptions } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
const createMockStore = (preloadedState = {}) => {
return configureStore({
reducer: {
user: (state = preloadedState.user) => state,
products: (state = preloadedState.products) => state,
ui: (state = preloadedState.ui) => state
}
});
};
interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
preloadedState?: any;
store?: any;
}
export function renderWithRedux(
element: ReactElement,
{ preloadedState, store = createMockStore(preloadedState), ...renderOptions }: CustomRenderOptions = {}
) {
function Wrapper({ children }: { children: ReactElement }) {
return <Provider store={store}>{children}</Provider>;
}
return { ...render(element, { wrapper: Wrapper, ...renderOptions }), store };
}
export function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export function waitForAsync() {
return new Promise(resolve => setTimeout(resolve, 0));
}
export const createMockFormData = (overrides = {}) => ({
name: 'Test Product',
quantity: 10,
category: 'Test',
sku: 'TEST-001',
price: 19.99,
description: 'Test',
...overrides
});Common Test Utilities
// Test factories
export const factories = {
createUser: (overrides?) => ({ id: '1', name: 'John', ...overrides }),
createProduct: (overrides?) => ({ id: '1', name: 'Test', ...overrides }),
createError: (status = 500) => ({ response: { status } })
};
// Assertion helpers
export const expectToBeCalledWithData = (
mockFn: any,
expectedData: any
) => {
expect(mockFn).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining(expectedData)
);
};
// Component testing helpers
export const findByRole = (container: any, role: string, name?: string) => {
const roleElement = container.querySelector(`[role="${role}"]`);
if (name && !roleElement?.textContent.includes(name)) return null;
return roleElement;
};Best Practices for Setup
✅ DO:
- Use MSW for HTTP mocking
- Clean up after each test
- Mock environment variables
- Reset mocks before each test
- Use test factories for data
- Implement global error handler
- Add accessibility matchers
❌ DON'T:
- Make real API calls
- Leave tests with side effects
- Skip cleanup
- Use hardcoded test data
- Ignore error handling
- Skip setup configuration
- Test multiple concerns
Related Documentation
- Overview - Testing overview
- Structure - Test organization
- Patterns - Testing patterns
- Coverage - Coverage goals
- Running Tests - Commands
Last Updated: November 2025