Skip to content
Test Runner Configuration logo

Test Runner Configuration

> **Skill Purpose:** Testing framework setup and test organization patterns

Coverage-Creatives/zeus0installs0stars

SKILL.md

Full skill instructions

Test Runner Configuration

Skill Purpose: Testing framework setup and test organization patterns


Core Skill Pattern

Objective: Establish comprehensive testing infrastructure with automated test execution, coverage reporting, and quality assurance.

Universal Pattern:

  1. Define testing strategy and coverage requirements
  2. Configure test runner and execution environment
  3. Set up test organization and naming conventions
  4. Establish test utilities and helper functions
  5. Create continuous testing and reporting procedures

Key Decisions (Project-Specific):

  • Testing framework selection and configuration
  • Test types and coverage requirements
  • Test organization and structure patterns
  • Automation and integration with CI/​CD
  • Reporting and quality gate integration

Project-Specific Implementation Notes

Customize per project:

  • Testing framework based on project needs and team preference
  • Coverage requirements based on criticality and resources
  • Test organization based on project complexity
  • Automation level based on development workflow
  • Reporting needs based on team size and stakeholder requirements

Example Implementation (Vitest/​Jest with Next.js Pattern)

Note: This is an example pattern using Vitest/​Jest. Adapt testing framework and configuration based on your specific project requirements and testing strategy.

Prerequisites (Example)

  • Project initialized
  • Testing strategy defined
  • Test types and coverage requirements established

Example: Vitest/​Jest Testing Framework Implementation

Framework-Specific Example: This demonstrates the pattern using Vitest/​Jest with Next.js. Adapt for your testing framework and project structure.

1. Install Testing Dependencies

# Install Vitest (recommended for new projects)
npm install -D vitest @vitest/​ui @vitest/​coverage-v8 @vitest/​coverage-istanbul

# Install Jest (alternative for existing projects)
npm install -D jest @jest/​environment-jsdom @jest/​transform-typescript @jest/​globals

# Install testing libraries
npm install -D @testing-library/​react @testing-library/​jest-dom @testing-library/​user-event
npm install -D @testing-library/​nextjs

# Install mock utilities
npm install -D msw node-fetch

# Install additional test utilities
npm install -D @types/​jest @types/​node-fetch
npm install -D jest-environment-jsdom @types/​jsdom

2. Configure Vitest (Recommended)

Create vitest.config.ts:

import { defineConfig } from 'vitest/​config';
import react from '@vitejs/​plugin-react';
import { resolve } from 'path';

export default defineConfig({
  plugins: [react()],
  test: {
    // Test environment
    environment: 'jsdom',
    setupFiles: ['./​test/​setup.ts'],
    
    // Global configuration
    globals: true,
    watch: false,
    
    // Coverage configuration
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: [
        'node_modules/',
        '.next/',
        'coverage/',
        '**/​*.d.ts',
        '**/​*.config.{js,ts}',
        '**/​stories/​**',
        '**/​__tests__/​**',
        '**/​test/​**',
        '**/​spec/​**',
      ],
      thresholds: {
        global: {
          branches: 80,
          functions: 80,
          lines: 80,
          statements: 80,
        },
      },
    },
    
    // Test file patterns
    include: [
      '**/​__tests__/​**/​*.{test,spec}.{js,ts,jsx,tsx}',
      '**/​*.{test,spec}.{js,ts,jsx,tsx}',
    ],
    exclude: [
      'node_modules/',
      '.next/',
      'dist/',
      'build/',
    ],
    
    // Reporter configuration
    reporter: ['verbose', 'json', 'html'],
    outputFile: {
      json: './​test-results/​results.json',
      html: './​test-results/​report.html',
    },
    
    // Performance settings
    threads: true,
    isolate: true,
    bail: 1,
    
    // Timeout settings
    testTimeout: 10000,
    hookTimeout: 10000,
  },
  
  // Resolve configuration
  resolve: {
    alias: {
      '@': resolve(__dirname, './​src'),
      '@/​components': resolve(__dirname, './​src/​components'),
      '@/​lib': resolve(__dirname, './​src/​lib'),
      '@/​app': resolve(__dirname, './​src/​app'),
      '@/​types': resolve(__dirname, './​src/​types'),
      '@/​utils': resolve(__dirname, './​src/​utils'),
      '@/​hooks': resolve(__dirname, './​src/​hooks'),
      '@/​config': resolve(__dirname, './​src/​config'),
    },
  },
});

3. Create Jest Configuration (Alternative)

Create jest.config.js:

const nextJest = require('next/​jest');

const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files
  dir: './',
});

// Add any custom config to be passed to Jest
const customJestConfig = {
  setupFilesAfterEnv: ['<rootDir>/​test/​setup.ts'],
  moduleNameMapping: {
    '^@/(.*)$': '<rootDir>/​src/​$1',
  },
  testEnvironment: 'jsdom',
  collectCoverageFrom: [
    'src/​**/​*.{js,jsx,ts,tsx}',
    '!src/​**/​*.d.ts',
    '!src/​**/​*.config.{js,ts}',
    '!src/​**/​stories/​**',
    '!src/​**/​__tests__/​**',
    '!src/​**/​test/​**',
    '!src/​**/​spec/​**',
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
  coverageReporters: ['text', 'lcov', 'html'],
  testMatch: [
    '**/​__tests__/​**/​*.{js,jsx,ts,tsx}',
    '**/​*.{test,spec}.{js,jsx,ts,tsx}',
  ],
  testPathIgnorePatterns: [
    'node_modules/',
    '.next/',
    'dist/',
    'build/',
  ],
  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/​babel'] }],
  },
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
  globals: {
    'ts-jest': {
      tsconfig: 'tsconfig.json',
    },
  },
};

// createJestConfig is exported this way to ensure that next/​jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig);

4. Create Test Setup Files

Create test/​setup.ts:

import '@testing-library/​jest-dom';
import { beforeAll, afterEach, afterAll } from 'vitest';
import { cleanup } from '@testing-library/​react';
import { server } from './​mocks/​server';

// Setup MSW
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));

// Reset request handlers after each test
afterEach(() => {
  server.resetHandlers();
  cleanup();
});

// Close server after all tests
afterAll(() => server.close());

// Mock Next.js router
vi.mock('next/​router', () => ({
  useRouter() {
    return {
      push: vi.fn(),
      replace: vi.fn(),
      prefetch: vi.fn(),
      back: vi.fn(),
      beforePopState: vi.fn(),
      events: {
        on: vi.fn(),
        off: vi.fn(),
        emit: vi.fn(),
      },
    };
  },
  usePathname() {
    return '/';
  },
  useSearchParams() {
    return new URLSearchParams();
  },
}));

// Mock Next.js image
vi.mock('next/​image', () => ({
  default: (props: any) => <img {...props} />,
}));

// Mock Next.js dynamic import
vi.mock('next/​dynamic', () => ({
  default: (fn: any) => fn(),
}));

// Mock IntersectionObserver
global.IntersectionObserver = vi.fn().mockImplementation(() => ({
  observe: vi.fn(),
  unobserve: vi.fn(),
  disconnect: vi.fn(),
}));

// Mock ResizeObserver
global.ResizeObserver = vi.fn().mockImplementation(() => ({
  observe: vi.fn(),
  unobserve: vi.fn(),
  disconnect: vi.fn(),
}));

// Mock matchMedia
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: vi.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: vi.fn(), // deprecated
    removeListener: vi.fn(), // deprecated
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
    dispatchEvent: vi.fn(),
  })),
});

// Mock localStorage
const localStorageMock = {
  getItem: vi.fn(),
  setItem: vi.fn(),
  removeItem: vi.fn(),
  clear: vi.fn(),
};
vi.stubGlobal('localStorage', localStorageMock);

// Mock sessionStorage
const sessionStorageMock = {
  getItem: vi.fn(),
  setItem: vi.fn(),
  removeItem: vi.fn(),
  clear: vi.fn(),
};
vi.stubGlobal('sessionStorage', sessionStorageMock);

// Mock fetch
global.fetch = vi.fn();

// Mock WebSocket
global.WebSocket = vi.fn().mockImplementation(() => ({
  close: vi.fn(),
  send: vi.fn(),
  addEventListener: vi.fn(),
  removeEventListener: vi.fn(),
  dispatchEvent: vi.fn(),
}));

5. Create MSW Mock Server

Create test/​mocks/​server.ts:

import { setupServer } from 'msw/​node';
import { rest } from 'msw';

// Mock API handlers
export const handlers = [
  // Authentication endpoints
  rest.post('/​api/​auth/​signin', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        user: {
          id: 'user-1',
          email: '[email protected]',
          name: 'Test User',
        },
        session: {
          expires: '2024-12-31T23:59:59.999Z',
        },
      })
    );
  }),

  rest.post('/​api/​auth/​signup', (req, res, ctx) => {
    return res(
      ctx.status(201),
      ctx.json({
        user: {
          id: 'user-1',
          email: '[email protected]',
          name: 'Test User',
        },
      })
    );
  }),

  rest.post('/​api/​auth/​signout', (req, res, ctx) => {
    return res(ctx.status(200));
  }),

  // User endpoints
  rest.get('/​api/​users/​profile', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        id: 'user-1',
        email: '[email protected]',
        name: 'Test User',
        avatar_url: null,
      })
    );
  }),

  rest.put('/​api/​users/​profile', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        id: 'user-1',
        email: '[email protected]',
        name: req.body.name || 'Test User',
        avatar_url: req.body.avatar_url || null,
      })
    );
  }),

  // Posts endpoints
  rest.get('/​api/​posts', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        posts: [
          {
            id: 'post-1',
            title: 'Test Post 1',
            content: 'This is a test post',
            published: true,
            author_id: 'user-1',
            created_at: '2024-01-01T00:00:00.000Z',
            updated_at: '2024-01-01T00:00:00.000Z',
          },
          {
            id: 'post-2',
            title: 'Test Post 2',
            content: 'This is another test post',
            published: false,
            author_id: 'user-1',
            created_at: '2024-01-02T00:00:00.000Z',
            updated_at: '2024-01-02T00:00:00.000Z',
          },
        ],
      })
    );
  }),

  rest.post('/​api/​posts', (req, res, ctx) => {
    return res(
      ctx.status(201),
      ctx.json({
        id: 'post-new',
        title: req.body.title,
        content: req.body.content,
        published: req.body.published || false,
        author_id: 'user-1',
        created_at: new Date().toISOString(),
        updated_at: new Date().toISOString(),
      })
    );
  }),

  rest.get('/​api/​posts/:id', (req, res, ctx) => {
    const { id } = req.params;
    
    if (id === 'post-1') {
      return res(
        ctx.status(200),
        ctx.json({
          id: 'post-1',
          title: 'Test Post 1',
          content: 'This is a test post',
          published: true,
          author_id: 'user-1',
          created_at: '2024-01-01T00:00:00.000Z',
          updated_at: '2024-01-01T00:00:00.000Z',
        })
      );
    }
    
    return res(ctx.status(404));
  }),

  // Error handlers
  rest.get('/​api/​error', (req, res, ctx) => {
    return res(
      ctx.status(500),
      ctx.json({ error: 'Internal server error' })
    );
  }),

  rest.get('/​api/​unauthorized', (req, res, ctx) => {
    return res(
      ctx.status(401),
      ctx.json({ error: 'Unauthorized' })
    );
  }),
];

// Create server
export const server = setupServer(...handlers);

6. Create Test Utilities

Create test/​utils.tsx:

import { render, RenderOptions } from '@testing-library/​react';
import { ReactElement } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/​react-query';
import { BrowserRouter } from 'react-router-dom';
import { AuthProvider } from '@/​contexts/​auth-context';

// Test query client
function createTestQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
        gcTime: 0,
      },
      mutations: {
        retry: false,
      },
    },
  });
}

// Custom render function
interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
  queryClient?: QueryClient;
  initialRoute?: string;
}

function customRender(
  ui: ReactElement,
  {
    queryClient = createTestQueryClient(),
    initialRoute = '/',
    ...renderOptions
  }: CustomRenderOptions = {}
) {
  function Wrapper({ children }: { children: React.ReactNode }) {
    return (
      <QueryClientProvider client={queryClient}>
        <BrowserRouter>
          <AuthProvider>
            {children}
          </​AuthProvider>
        </​BrowserRouter>
      </​QueryClientProvider>
    );
  }

  return {
    ...render(ui, { wrapper: Wrapper, ...renderOptions }),
    queryClient,
  };
}

// Re-export everything from Testing Library
export * from '@testing-library/​react';
export { customRender as render };

// Mock data generators
export const createMockUser = (overrides = {}) => ({
  id: 'user-1',
  email: '[email protected]',
  name: 'Test User',
  avatar_url: null,
  created_at: '2024-01-01T00:00:00.000Z',
  updated_at: '2024-01-01T00:00:00.000Z',
  ...overrides,
});

export const createMockPost = (overrides = {}) => ({
  id: 'post-1',
  title: 'Test Post',
  content: 'This is a test post',
  published: true,
  author_id: 'user-1',
  created_at: '2024-01-01T00:00:00.000Z',
  updated_at: '2024-01-01T00:00:00.000Z',
  ...overrides,
});

// Test helpers
export const waitForLoadingToFinish = () =>
  new Promise(resolve => setTimeout(resolve, 0));

export const flushPromises = () => new Promise(resolve => setTimeout(resolve, 0));

// Mock functions
export const mockPush = vi.fn();
export const mockReplace = vi.fn();
export const mockPrefetch = vi.fn();

// Mock router
export const mockRouter = {
  push: mockPush,
  replace: mockReplace,
  prefetch: mockPrefetch,
  back: vi.fn(),
  beforePopState: vi.fn(),
  events: {
    on: vi.fn(),
    off: vi.fn(),
    emit: vi.fn(),
  },
};

// Async test utilities
export const act = async (callback: () => void | Promise<void>) => {
  await callback();
  await flushPromises();
};

// Component test helpers
export const renderWithProviders = (
  component: ReactElement,
  options?: CustomRenderOptions
) => {
  return customRender(component, options);
};

// Form test helpers
export const fillForm = async (
  getByLabelText: (text: string) => HTMLElement,
  formData: Record<string, string>
) => {
  for (const [label, value] of Object.entries(formData)) {
    const input = getByLabelText(label) as HTMLInputElement;
    await act(async () => {
      input.value = value;
      input.dispatchEvent(new Event('input', { bubbles: true }));
    });
  }
};

export const submitForm = async (
  getByRole: (role: string, options?: any) => HTMLElement
) => {
  const submitButton = getByRole('button', { name: /​submit/​i }) as HTMLButtonElement;
  await act(async () => {
    submitButton.click();
  });
};

7. Create Example Tests

Create test/​components/​Button.test.tsx:

import { render, screen, fireEvent } from '@testing-library/​react';
import { Button } from '@/​components/​ui/​button';
import { createMockUser } from '../​utils';

describe('Button Component', () => {
  it('renders correctly with default props', () => {
    render(<Button>Click me</​Button>);
    
    const button = screen.getByRole('button', { name: /​click me/​i });
    expect(button).toBeInTheDocument();
    expect(button).toHaveClass('btn');
    expect(button).toHaveClass('btn-primary');
  });

  it('renders with different variants', () => {
    render(<Button variant="destructive">Delete</​Button>);
    
    const button = screen.getByRole('button', { name: /​delete/​i });
    expect(button).toHaveClass('btn-destructive');
  });

  it('handles click events', async () => {
    const handleClick = vi.fn();
    render(<Button onClick={handleClick}>Click me</​Button>);
    
    const button = screen.getByRole('button', { name: /​click me/​i });
    
    await fireEvent.click(button);
    
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('is disabled when loading', () => {
    render(<Button loading>Loading...</​Button>);
    
    const button = screen.getByRole('button', { name: /​loading/​i });
    expect(button).toBeDisabled();
  });

  it('applies custom className', () => {
    render(<Button className="custom-class">Click me</​Button>);
    
    const button = screen.getByRole('button', { name: /​click me/​i });
    expect(button).toHaveClass('custom-class');
  });
});

Create test/​hooks/​useAuth.test.ts:

import { renderHook, waitFor } from '@testing-library/​react';
import { useAuth } from '@/​hooks/​use-auth';
import { createMockUser } from '../​utils';
import { server } from '../​mocks/​server';

// Mock server responses
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe('useAuth Hook', () => {
  it('returns initial loading state', () => {
    const { result } = renderHook(() => useAuth());
    
    expect(result.current.loading).toBe(true);
    expect(result.current.user).toBe(null);
    expect(result.current.session).toBe(null);
  });

  it('loads user session', async () => {
    // Mock successful session
    server.use(
      rest.get('/​api/​auth/​session', (req, res, ctx) => {
        return res(
          ctx.status(200),
          ctx.json({
            user: createMockUser(),
            expires: '2024-12-31T23:59:59.999Z',
          })
        );
      })
    );

    const { result } = renderHook(() => useAuth());

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
      expect(result.current.user).toEqual(createMockUser());
      expect(result.current.session).toBeDefined();
    });
  });

  it('handles sign in', async () => {
    const { result } = renderHook(() => useAuth());

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    const { signIn } = result.current;

    const signInResult = await signIn('[email protected]', 'password');
    
    expect(signInResult.error).toBeUndefined();
    expect(signInResult.data).toBeDefined();
  });

  it('handles sign in errors', async () => {
    // Mock sign in error
    server.use(
      rest.post('/​api/​auth/​signin', (req, res, ctx) => {
        return res(
          ctx.status(401),
          ctx.json({ error: 'Invalid credentials' })
        );
      })
    );

    const { result } = renderHook(() => useAuth());

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    const { signIn } = result.current;

    const signInResult = await signIn('[email protected]', 'wrong-password');
    
    expect(signInResult.error).toBe('Invalid credentials');
  });

  it('handles sign out', async () => {
    const { result } = renderHook(() => useAuth());

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    const { signOut } = result.current;

    await signOut();

    expect(result.current.user).toBeNull();
    expect(result.current.session).toBeNull();
  });
});

Create test/​api/​posts.test.ts:

import { createClient } from '@/​lib/​supabase/​client';
import { createMockPost } from '../​utils';
import { server } from '../​mocks/​server';

// Mock Supabase client
vi.mock('@/​lib/​supabase/​client', () => ({
  createClient: vi.fn(() => ({
    from: vi.fn(() => ({
      select: vi.fn(() => ({
        data: [createMockPost()],
        error: null,
      })),
      insert: vi.fn(() => ({
        data: createMockPost(),
        error: null,
      })),
      update: vi.fn(() => ({
        data: createMockPost(),
        error: null,
      })),
      delete: vi.fn(() => ({
        data: createMockPost(),
        error: null,
      })),
    })),
  })),
}));

describe('Posts API', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('fetches posts successfully', async () => {
    const supabase = createClient();
    
    const { data, error } = await supabase
      .from('posts')
      .select('*');

    expect(data).toEqual([createMockPost()]);
    expect(error).toBeNull();
  });

  it('creates a new post', async () => {
    const supabase = createClient();
    const newPost = {
      title: 'New Post',
      content: 'New content',
      published: true,
    };

    const { data, error } = await supabase
      .from('posts')
      .insert(newPost)
      .select()
      .single();

    expect(data).toEqual(createMockPost(newPost));
    expect(error).toBeNull();
  });

  it('handles API errors', async () => {
    // Mock error response
    server.use(
      rest.get('/​api/​posts', (req, res, ctx) => {
        return res(
          ctx.status(500),
          ctx.json({ error: 'Internal server error' })
        );
      })
    );

    const response = await fetch('/​api/​posts');
    expect(response.status).toBe(500);
  });
});

8. Create Test Scripts

Create scripts/​test-runner.js:

#!/​usr/​bin/​env node

const { execSync } = require('child_process');
const fs = require('fs');

// ANSI color codes
const colors = {
  reset: '\x1b[0m',
  red: '\x1b[31m',
  green: '\x1b[32m',
  yellow: '\x1b[33m',
  blue: '\x1b[34m',
  cyan: '\x1b[36c',
  magenta: '\x1b[35m',
};

function colorLog(message, color = 'reset') {
  console.log(`${colors[color]}${message}${colors.reset}`);
}

function runCommand(command, description) {
  try {
    colorLog(`🔍 Running: ${description}`, 'blue');
    execSync(command, { stdio: 'inherit' });
    colorLog(`✅ ${description} - PASSED`, 'green');
    return true;
  } catch (error) {
    colorLog(`❌ ${description} - FAILED`, 'red');
    return false;
  }
}

function main() {
  const args = process.argv.slice(2);
  const testType = args[0] || 'all';
  
  colorLog('🧪 Running Test Suite', 'magenta');
  colorLog('====================', 'magenta');
  
  let success = true;
  
  switch (testType) {
    case 'unit':
      success = runCommand('npm run test:unit', 'Unit tests');
      break;
      
    case 'integration':
      success = runCommand('npm run test:integration', 'Integration tests');
      break;
      
    case 'e2e':
      success = runCommand('npm run test:e2e', 'End-to-end tests');
      break;
      
    case 'coverage':
      success = runCommand('npm run test:coverage', 'Test coverage');
      break;
      
    case 'watch':
      success = runCommand('npm run test:watch', 'Watch mode');
      break;
      
    case 'ci':
      success = runCommand('npm run test:ci', 'CI tests');
      break;
      
    case 'all':
    default:
      success = runCommand('npm run test', 'All tests');
      break;
  }
  
  colorLog('\n📊 Test Summary', 'magenta');
  colorLog('================', 'magenta');
  
  if (success) {
    colorLog('✅ All tests passed!', 'green');
    process.exit(0);
  } else {
    colorLog('❌ Some tests failed!', 'red');
    process.exit(1);
  }
}

if (require.main === module) {
  main();
}

module.exports = { runCommand };

9. Update Package.json Scripts

Update package.json scripts:

{
  "scripts": {
    "test": "vitest",
    "test:unit": "vitest run --config vitest.config.unit.ts",
    "test:integration": "vitest run --config vitest.config.integration.ts",
    "test:e2e": "playwright test",
    "test:watch": "vitest",
    "test:coverage": "vitest run --coverage",
    "test:ui": "vitest --ui",
    "test:ci": "vitest run --coverage --reporter=junit --outputFile=./​test-results/​junit.xml",
    "test:debug": "vitest --inspect-brk --no-coverage",
    "test:runner": "node scripts/​test-runner.js",
    "test:clean": "rimraf coverage test-results"
  }
}

10. Create Test Configuration Files

Create vitest.config.unit.ts:

import { defineConfig } from 'vitest/​config';

export default defineConfig({
  test: {
    environment: 'jsdom',
    include: ['**/​__tests__/​**/​*.unit.{test,spec}.{js,ts,jsx,tsx}'],
    exclude: ['**/​integration/​**', '**/​e2e/​**'],
    coverage: {
      include: ['src/​**/​*.{js,ts,jsx,tsx}'],
      exclude: ['**/​*.d.ts', '**/​stories/​**'],
    },
  },
});

Create vitest.config.integration.ts:

import { defineConfig } from 'vitest/​config';

export default defineConfig({
  test: {
    environment: 'jsdom',
    include: ['**/​__tests__/​**/​*.integration.{test,spec}.{js,ts,jsx,tsx}'],
    exclude: ['**/​unit/​**', '**/​e2e/​**'],
    setupFiles: ['./​test/​integration-setup.ts'],
    coverage: {
      include: ['src/​**/​*.{js,ts,jsx,tsx}'],
      exclude: ['**/​*.d.ts', '**/​stories/​**'],
    },
  },
});

Code Examples

Running Tests

# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

# Run tests with UI
npm run test:ui

# Run specific test types
npm run test:unit
npm run test:integration
npm run test:e2e

# Run tests in CI mode
npm run test:ci

# Debug tests
npm run test:debug

Writing Component Tests

// test/​components/​Card.test.tsx
import { render, screen } from '@testing-library/​react';
import { Card } from '@/​components/​ui/​card';

describe('Card Component', () => {
  it('renders with title and content', () => {
    render(
      <Card>
        <CardHeader>
          <CardTitle>Test Title</​CardTitle>
        </​CardHeader>
        <CardContent>
          <p>Test content</​p>
        </​CardContent>
      </​Card>
    );

    expect(screen.getByText('Test Title')).toBeInTheDocument();
    expect(screen.getByText('Test content')).toBeInTheDocument();
  });
});

Writing Hook Tests

// test/​hooks/​useCounter.test.ts
import { renderHook, act } from '@testing-library/​react';
import { useCounter } from '@/​hooks/​useCounter';

describe('useCounter Hook', () => {
  it('initializes with default value', () => {
    const { result } = renderHook(() => useCounter());
    
    expect(result.current.count).toBe(0);
  });

  it('increments count', () => {
    const { result } = renderHook(() => useCounter());
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });
});

Writing API Tests

// test/​api/​users.test.ts
import { createServerClient } from '@/​lib/​supabase/​server';
import { server } from '../​mocks/​server';

describe('Users API', () => {
  it('fetches user profile', async () => {
    const supabase = createServerClient();
    
    const { data, error } = await supabase
      .from('profiles')
      .select('*')
      .eq('user_id', 'user-1')
      .single();

    expect(data).toBeDefined();
    expect(error).toBeNull();
  });
});

Configuration Templates

Complete vitest.config.ts

import { defineConfig } from 'vitest/​config';
import react from '@vitejs/​plugin-react';
import { resolve } from 'path';

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    setupFiles: ['./​test/​setup.ts'],
    globals: true,
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      thresholds: {
        global: {
          branches: 80,
          functions: 80,
          lines: 80,
          statements: 80,
        },
      },
    },
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, './​src'),
    },
  },
});

Best Practices

  1. Use Vitest for new projects - Faster and more modern
  2. Mock external dependencies - Isolate unit tests
  3. Use MSW for API mocking - Realistic API simulation
  4. Write descriptive tests - Clear test purpose
  5. Use proper test structure - Arrange, Act, Assert
  6. Maintain good coverage - 80% minimum threshold
  7. Test components and hooks - Full coverage
  8. Use test utilities - Reduce boilerplate code

Stop Conditions

STOP and report if:

  • Test runner installation fails
  • Configuration errors occur
  • Mock setup fails
  • Test execution errors

Expected Outcomes:

  • Test runner configured correctly
  • Mock server functional
  • Test utilities working
  • Coverage reporting active
  • CI integration working

Verification Checklist

  • Test runner installed and configured
  • Mock server set up correctly
  • Test utilities created
  • Example tests functional
  • Coverage reporting working
  • Package.json scripts updated
  • CI configuration working
  • Test runner scripts functional
  • All test types configured
  • Mock data generators working

Version: 1.0.0 Last Updated: 2026-01-31 Skill Category: Architecture - CI