# Testing Patterns

> When to activate: Vitest, Testing Library, MSW, Playwright, visual regression, a11y testing, component tests, E2E tests

- Skill: `mattakushi432/testing-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/testing-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/testing-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/testing-patterns

---

# Frontend Testing Patterns

## Vitest + Testing Library

```tsx
// Button.test.tsx
import { render, screen, userEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { Button } from './Button';

describe('Button', () => {
  it('calls onClick when clicked', async () => {
    const user = userEvent.setup();
    const onClick = vi.fn();
    render(<Button onClick={onClick}>Submit</Button>);
    await user.click(screen.getByRole('button', { name: 'Submit' }));
    expect(onClick).toHaveBeenCalledOnce();
  });

  it('is disabled when loading', () => {
    render(<Button loading>Submit</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
  });
});
```

## MSW API Mocking

```ts
// mocks/handlers.ts
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('/api/users', () =>
    HttpResponse.json([{ id: 1, name: 'Alice' }])
  ),
  http.post('/api/users', async ({ request }) => {
    const body = await request.json();
    return HttpResponse.json({ id: 2, ...body }, { status: 201 });
  }),
  http.get('/api/error', () =>
    HttpResponse.json({ message: 'Server error' }, { status: 500 })
  ),
];

// mocks/setup.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);

// vitest.setup.ts
import { server } from './mocks/setup';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
```

## Async State Testing

```tsx
it('displays users after loading', async () => {
  render(<UserList />);
  expect(screen.getByRole('status')).toBeInTheDocument(); // spinner
  await screen.findByText('Alice'); // waits for async
  expect(screen.queryByRole('status')).not.toBeInTheDocument();
});

it('shows error on failure', async () => {
  server.use(http.get('/api/users', () =>
    HttpResponse.json({ message: 'Error' }, { status: 500 })
  ));
  render(<UserList />);
  await screen.findByRole('alert');
  expect(screen.getByText(/something went wrong/i)).toBeInTheDocument();
});
```

## Playwright E2E

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

test.describe('Checkout flow', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/shop');
  });

  test('completes purchase', async ({ page }) => {
    await page.getByRole('button', { name: 'Add to cart' }).first().click();
    await page.getByRole('link', { name: 'Cart' }).click();
    await expect(page.getByText('1 item')).toBeVisible();
    await page.getByRole('button', { name: 'Checkout' }).click();
    await page.getByLabel('Email').fill('test@example.com');
    await page.getByRole('button', { name: 'Pay' }).click();
    await expect(page.getByRole('heading', { name: 'Order confirmed' })).toBeVisible();
  });
});

// playwright.config.ts
export default {
  use: { baseURL: 'http://localhost:3000', screenshot: 'only-on-failure' },
  webServer: { command: 'npm run dev', url: 'http://localhost:3000' },
};
```

## Accessibility Testing

```ts
import { axe } from 'jest-axe';

it('has no accessibility violations', async () => {
  const { container } = render(<LoginForm />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

// Playwright a11y
import { checkA11y } from 'axe-playwright';
test('home page is accessible', async ({ page }) => {
  await page.goto('/');
  await checkA11y(page, undefined, { detailedReport: true });
});
```

## Visual Regression (Playwright)

```ts
test('hero section matches snapshot', async ({ page }) => {
  await page.goto('/');
  await page.waitForLoadState('networkidle');
  await expect(page.locator('.hero')).toHaveScreenshot('hero.png', {
    maxDiffPixelRatio: 0.01,
  });
});
```

## Custom Render with Providers

```tsx
// test-utils.tsx
import { render, RenderOptions } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

function AllProviders({ children }) {
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  return <QueryClientProvider client={qc}>{children}</QueryClientProvider>;
}

export function renderWithProviders(ui: React.ReactElement, options?: RenderOptions) {
  return render(ui, { wrapper: AllProviders, ...options });
}
```

