DEV Community

Thesius Code
Thesius Code

Posted on • Originally published at datanest-stores.pages.dev

Frontend Testing Toolkit

Frontend Testing Toolkit

A complete testing infrastructure for React and Next.js applications that covers every layer of the testing pyramid. Includes pre-configured Vitest for lightning-fast unit tests, Testing Library patterns for component integration tests, Playwright scripts for cross-browser E2E testing, and Storybook interaction tests for visual regression. Ships with 50+ test templates, custom matchers, mock factories, and CI/CD workflows — so you can go from zero test coverage to 80%+ in a weekend, not a quarter.

Key Features

  • Vitest Configuration — Optimized config with path aliases, coverage thresholds, watch mode, and React/JSX transform via SWC
  • Testing Library Patterns — 30+ component test templates using user-centric queries (getByRole, findByText) instead of implementation details
  • Playwright E2E Suite — Cross-browser test scripts for authentication flows, form submissions, navigation, and responsive behavior
  • Storybook Integration — Interaction tests that run inside stories, combining visual review with automated assertions
  • Mock Factory System — Type-safe factories for generating test data with overrides, relations, and sequences
  • Custom Test Matchers — Domain-specific matchers like toBeAccessible(), toHaveFormError(), and toMatchRouteSnapshot()
  • CI/CD Workflows — GitHub Actions pipelines for unit tests, E2E tests, visual regression, and coverage reporting
  • MSW Request Mocking — Mock Service Worker handlers for API mocking in tests and Storybook without changing application code

Quick Start

  1. Install the testing stack:
npm install -D vitest @testing-library/react @testing-library/jest-dom \
  @testing-library/user-event @playwright/test msw jsdom
Enter fullscreen mode Exit fullscreen mode
  1. Copy testing/ into your project and add the Vitest config:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react-swc';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
  plugins: [react(), tsconfigPaths()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./testing/setup.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'lcov', 'html'],
      thresholds: { statements: 80, branches: 75, functions: 80, lines: 80 },
    },
    include: ['src/**/*.test.{ts,tsx}'],
  },
});
Enter fullscreen mode Exit fullscreen mode
  1. Run your first test:
npx vitest run          # single run
npx vitest --watch      # watch mode
npx vitest --coverage   # with coverage report
Enter fullscreen mode Exit fullscreen mode

Architecture / How It Works

frontend-testing-toolkit/
├── setup/              # setup.ts, msw-handlers.ts, test-utils.tsx
├── unit/               # Hook, utility, and store test patterns
├── integration/        # Form, navigation, and data-fetching tests
├── e2e/                # Playwright config, auth/checkout specs, page objects
├── factories/          # Type-safe test data factories (user, product)
├── matchers/           # Custom matchers (accessibility, form)
└── ci/                 # GitHub Actions workflows (test.yml, e2e.yml)
Enter fullscreen mode Exit fullscreen mode

Usage Examples

Custom Render with Providers

import { render, RenderOptions } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

export function renderWithProviders(ui: React.ReactElement, options?: Omit<RenderOptions, 'wrapper'>) {
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 } } });
  function Wrapper({ children }: { children: React.ReactNode }) {
    return <QueryClientProvider client={qc}>{children}</QueryClientProvider>;
  }
  return { ...render(ui, { wrapper: Wrapper, ...options }), queryClient: qc };
}
Enter fullscreen mode Exit fullscreen mode

Playwright E2E Test

// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Authentication', () => {
  test('user can log in and see dashboard', async ({ page }) => {
    await page.goto('/login');
    await page.getByLabel('Email').fill('user@example.com');
    await page.getByLabel('Password').fill('SecurePass123');
    await page.getByRole('button', { name: 'Sign In' }).click();
    await expect(page).toHaveURL('/dashboard');
    await expect(page.getByRole('heading', { name: 'Welcome back' })).toBeVisible();
  });
});
Enter fullscreen mode Exit fullscreen mode

Type-Safe Test Data Factory

let sequence = 0;
interface User { id: string; name: string; email: string; role: 'admin' | 'user'; }

export function createUser(overrides?: Partial<User>): User {
  sequence++;
  return {
    id: `user-${sequence}`, name: `Test User ${sequence}`,
    email: `user${sequence}@example.com`, role: 'user', ...overrides,
  };
}
Enter fullscreen mode Exit fullscreen mode

Configuration

Playwright Multi-Browser Config

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  webServer: {
    command: 'npm run dev',
    port: 3000,
    reuseExistingServer: !process.env.CI,
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'mobile', use: { ...devices['iPhone 14'] } },
  ],
});
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Query by role, not by test IDgetByRole('button', { name: 'Submit' }) tests what users see; getByTestId tests implementation
  • Use userEvent over fireEventuserEvent.type() simulates real keystrokes including focus, keydown, input, and keyup events
  • Create one factory per entity — Consistent test data across your entire test suite eliminates "where did this mock come from?" questions
  • Mock at the network layer — MSW intercepts fetch calls, so your components use the same code paths they do in production
  • Run Playwright against a preview build — Test the production bundle, not dev mode, to catch build-time issues early
  • Set coverage thresholds in CI — Prevent coverage from silently decreasing by failing the build when thresholds aren't met

Troubleshooting

Issue Cause Fix
document is not defined in Vitest Missing environment: 'jsdom' in config Add environment: 'jsdom' to vitest.config.ts
Tests pass locally but fail in CI Timing-sensitive assertions without await Use findBy* queries (they wait) instead of getBy* for async content
Playwright can't find elements Page not fully loaded before assertion Add await page.waitForLoadState('networkidle') before assertions
MSW handlers not intercepting requests MSW server not started in test setup Add server.listen() in beforeAll and server.close() in afterAll

This is 1 of 11 resources in the Frontend Developer Pro toolkit. Get the complete [Frontend Testing Toolkit] with all files, templates, and documentation for $29.

Get the Full Kit →

Or grab the entire Frontend Developer Pro bundle (11 products) for $129 — save 30%.

Get the Complete Bundle →


Related Articles

Top comments (0)