# Testing Setup Reference Complete configuration guide for Vitest, React Testing Library, and MSW in TanStack projects. ## Vitest Configuration ### vitest.config.ts ```typescript 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/test/setup.ts'], coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], exclude: [ 'node_modules/', 'src/test/', '**/*.d.ts', '**/*.config.*', '**/mockData', ], thresholds: { lines: 80, functions: 80, branches: 80, statements: 80, }, }, }, resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, }); ``` ### Key Options | Option | Purpose | Value | |--------|---------|-------| | `globals` | Enable global test APIs (describe, it, expect) | `true` | | `environment` | Test environment (jsdom for DOM testing) | `'jsdom'` | | `setupFiles` | Files to run before each test file | `['./src/test/setup.ts']` | | `coverage.provider` | Coverage provider | `'v8'` (faster) | | `coverage.thresholds` | Minimum coverage percentages | 80% recommended | ## Test Setup File ### src/test/setup.ts ```typescript import '@testing-library/jest-dom/vitest'; import { cleanup } from '@testing-library/react'; import { afterEach, beforeAll, afterAll, vi } from 'vitest'; import { server } from './msw/server'; // Cleanup after each test afterEach(() => { cleanup(); }); // MSW setup beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); // Mock window.matchMedia (for responsive components) 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 (for infinite scroll) global.IntersectionObserver = vi.fn().mockImplementation(() => ({ observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn(), })); // Mock ResizeObserver (for table columns) global.ResizeObserver = vi.fn().mockImplementation(() => ({ observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn(), })); // Suppress console errors in tests (optional) const originalError = console.error; beforeAll(() => { console.error = (...args: any[]) => { if ( typeof args[0] === 'string' && args[0].includes('Warning: ReactDOM.render') ) { return; } originalError.call(console, ...args); }; }); afterAll(() => { console.error = originalError; }); ``` ## MSW Setup ### src/test/msw/server.ts ```typescript import { setupServer } from 'msw/node'; import { handlers } from './handlers'; export const server = setupServer(...handlers); ``` ### src/test/msw/handlers.ts ```typescript import { http, HttpResponse } from 'msw'; export const handlers = [ // Users API http.get('/api/users', () => { return HttpResponse.json([ { id: '1', name: 'Alice', email: 'alice@example.com' }, { id: '2', name: 'Bob', email: 'bob@example.com' }, ]); }), http.get('/api/users/:id', ({ params }) => { return HttpResponse.json({ id: params.id, name: 'Alice', email: 'alice@example.com', }); }), http.post('/api/users', async ({ request }) => { const body = await request.json(); return HttpResponse.json( { id: '3', ...body }, { status: 201 } ); }), // Auth API http.post('/api/auth/login', async ({ request }) => { const { email, password } = await request.json(); if (email === 'test@example.com' && password === 'password') { return HttpResponse.json({ token: 'mock-jwt-token', user: { id: '1', email, name: 'Test User' }, }); } return HttpResponse.json( { error: 'Invalid credentials' }, { status: 401 } ); }), ]; ``` ### Overriding Handlers in Tests ```typescript import { server } from '../test/msw/server'; import { http, HttpResponse } from 'msw'; it('handles API error', async () => { // Override handler for this test server.use( http.get('/api/users', () => { return new HttpResponse(null, { status: 500 }); }) ); // Test error handling... }); ``` ## TanStack Query Setup ### src/test/query-client.ts ```typescript import { QueryClient } from '@tanstack/react-query'; export function createTestQueryClient() { return new QueryClient({ defaultOptions: { queries: { retry: false, // Don't retry failed queries in tests gcTime: 0, // No garbage collection staleTime: 0, // Always consider data stale }, mutations: { retry: false, }, }, logger: { log: console.log, warn: console.warn, error: () => {}, // Silence error logs in tests }, }); } ``` ## TanStack Router Setup ### src/test/router-utils.tsx ```typescript import { createMemoryHistory, createRouter } from '@tanstack/react-router'; import { routeTree } from '../routeTree.gen'; export function createTestRouter(initialEntries = ['/']) { const history = createMemoryHistory({ initialEntries }); return createRouter({ routeTree, history, context: { // Mock auth context auth: { isAuthenticated: true, user: { id: '1', name: 'Test User' }, }, }, }); } ``` ## Custom Test Utilities ### src/test/test-utils.tsx ```typescript import { ReactElement } from 'react'; import { render, RenderOptions } from '@testing-library/react'; import { QueryClientProvider } from '@tanstack/react-query'; import { RouterProvider } from '@tanstack/react-router'; import { createTestQueryClient } from './query-client'; import { createTestRouter } from './router-utils'; interface WrapperProps { children: React.ReactNode; } export function AllTheProviders({ children }: WrapperProps) { const queryClient = createTestQueryClient(); const router = createTestRouter(); return ( {children} ); } export function renderWithProviders( ui: ReactElement, options?: Omit ) { return render(ui, { wrapper: AllTheProviders, ...options }); } // Re-export everything from React Testing Library export * from '@testing-library/react'; export { renderWithProviders as render }; ``` ## Package.json Scripts ```json { "scripts": { "test": "vitest", "test:ui": "vitest --ui", "test:coverage": "vitest --coverage", "test:run": "vitest run" }, "devDependencies": { "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.1", "@vitest/coverage-v8": "^1.0.4", "@vitest/ui": "^1.0.4", "jsdom": "^23.0.1", "msw": "^2.0.11", "vitest": "^1.0.4" } } ``` ## TypeScript Configuration ### tsconfig.json ```json { "compilerOptions": { "types": ["vitest/globals", "@testing-library/jest-dom"] } } ``` ## Coverage Configuration ### .gitignore ``` coverage/ .vitest/ ``` ### Coverage Thresholds ```typescript // vitest.config.ts export default defineConfig({ test: { coverage: { thresholds: { lines: 80, functions: 80, branches: 80, statements: 80, // Per-file thresholds perFile: true, }, }, }, }); ``` ## Environment Variables ### .env.test ```bash VITE_API_URL=http://localhost:3000/api VITE_ENV=test ``` ### Loading in Tests ```typescript // src/test/setup.ts import { loadEnv } from 'vite'; const env = loadEnv('test', process.cwd(), ''); process.env = { ...process.env, ...env }; ``` ## CI/CD Integration ### GitHub Actions ```yaml name: Test on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '20' - run: npm ci - run: npm run test:coverage - uses: codecov/codecov-action@v3 with: files: ./coverage/coverage-final.json ``` ## Troubleshooting ### Common Issues **Issue**: `ReferenceError: describe is not defined` **Solution**: Add `globals: true` to vitest config **Issue**: `Cannot find module '@testing-library/jest-dom/vitest'` **Solution**: Install `@testing-library/jest-dom` package **Issue**: MSW not intercepting requests **Solution**: Ensure `server.listen()` is called in `beforeAll` **Issue**: Tests fail with "Act" warnings **Solution**: Wrap async operations with `waitFor` or `findBy` --- **Next**: [Testing Best Practices](testing-best-practices.md) | **Index**: [Reference Index](INDEX.md)