addyosmani--agent-skills
fea75b1647
Update all SKILL.md descriptions to lead with what the skill does (third person) followed by trigger conditions, per Anthropic's skill authoring docs. All original trigger conditions preserved. Update description max chars to match Anthropic spec (1024). Retain workflow-summary warning in skill-anatomy.md to prevent agents from following descriptions instead of reading full skills. Add Table of Contents to reference files longer than 100 lines so Claude can see the full scope of available information even when previewing with partial reads.
6.8 KiB
6.8 KiB
Testing Patterns Reference
Quick reference for common testing patterns across the stack. Use alongside the test-driven-development skill.
Table of Contents
- Test Structure (Arrange-Act-Assert)
- Test Naming Conventions
- Common Assertions
- Mocking Patterns
- React/Component Testing
- API / Integration Testing
- E2E Testing (Playwright)
- Test Anti-Patterns
Test Structure (Arrange-Act-Assert)
it('describes expected behavior', () => {
// Arrange: Set up test data and preconditions
const input = { title: 'Test Task', priority: 'high' };
// Act: Perform the action being tested
const result = createTask(input);
// Assert: Verify the outcome
expect(result.title).toBe('Test Task');
expect(result.priority).toBe('high');
expect(result.status).toBe('pending');
});
Test Naming Conventions
// Pattern: [unit] [expected behavior] [condition]
describe('TaskService.createTask', () => {
it('creates a task with default pending status', () => {});
it('throws ValidationError when title is empty', () => {});
it('trims whitespace from title', () => {});
it('generates a unique ID for each task', () => {});
});
Common Assertions
// Equality
expect(result).toBe(expected); // Strict equality (===)
expect(result).toEqual(expected); // Deep equality (objects/arrays)
expect(result).toStrictEqual(expected); // Deep equality + type matching
// Truthiness
expect(result).toBeTruthy();
expect(result).toBeFalsy();
expect(result).toBeNull();
expect(result).toBeDefined();
expect(result).toBeUndefined();
// Numbers
expect(result).toBeGreaterThan(5);
expect(result).toBeLessThanOrEqual(10);
expect(result).toBeCloseTo(0.3, 5); // Floating point
// Strings
expect(result).toMatch(/pattern/);
expect(result).toContain('substring');
// Arrays / Objects
expect(array).toContain(item);
expect(array).toHaveLength(3);
expect(object).toHaveProperty('key', 'value');
// Errors
expect(() => fn()).toThrow();
expect(() => fn()).toThrow(ValidationError);
expect(() => fn()).toThrow('specific message');
// Async
await expect(asyncFn()).resolves.toBe(value);
await expect(asyncFn()).rejects.toThrow(Error);
Mocking Patterns
Mock Functions
const mockFn = jest.fn();
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue({ data: 'test' });
mockFn.mockImplementation((x) => x * 2);
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
expect(mockFn).toHaveBeenCalledTimes(3);
Mock Modules
// Mock an entire module
jest.mock('./database', () => ({
query: jest.fn().mockResolvedValue([{ id: 1, title: 'Test' }]),
}));
// Mock specific exports
jest.mock('./utils', () => ({
...jest.requireActual('./utils'),
generateId: jest.fn().mockReturnValue('test-id'),
}));
Mock at Boundaries Only
Mock these: Don't mock these:
├── Database calls ├── Internal utility functions
├── HTTP requests ├── Business logic
├── File system operations ├── Data transformations
├── External API calls ├── Validation functions
└── Time/Date (when needed) └── Pure functions
React/Component Testing
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
describe('TaskForm', () => {
it('submits the form with entered data', async () => {
const onSubmit = jest.fn();
render(<TaskForm onSubmit={onSubmit} />);
// Find elements by accessible role/label (not test IDs)
await screen.findByRole('textbox', { name: /title/i });
fireEvent.change(screen.getByRole('textbox', { name: /title/i }), {
target: { value: 'New Task' },
});
fireEvent.click(screen.getByRole('button', { name: /create/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({ title: 'New Task' });
});
});
it('shows validation error for empty title', async () => {
render(<TaskForm onSubmit={jest.fn()} />);
fireEvent.click(screen.getByRole('button', { name: /create/i }));
expect(await screen.findByText(/title is required/i)).toBeInTheDocument();
});
});
API / Integration Testing
import request from 'supertest';
import { app } from '../src/app';
describe('POST /api/tasks', () => {
it('creates a task and returns 201', async () => {
const response = await request(app)
.post('/api/tasks')
.send({ title: 'Test Task' })
.set('Authorization', `Bearer ${testToken}`)
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(String),
title: 'Test Task',
status: 'pending',
});
});
it('returns 422 for invalid input', async () => {
const response = await request(app)
.post('/api/tasks')
.send({ title: '' })
.set('Authorization', `Bearer ${testToken}`)
.expect(422);
expect(response.body.error.code).toBe('VALIDATION_ERROR');
});
it('returns 401 without authentication', async () => {
await request(app)
.post('/api/tasks')
.send({ title: 'Test' })
.expect(401);
});
});
E2E Testing (Playwright)
import { test, expect } from '@playwright/test';
test('user can create and complete a task', async ({ page }) => {
// Navigate and authenticate
await page.goto('/');
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'testpass123');
await page.click('button:has-text("Log in")');
// Create a task
await page.click('button:has-text("New Task")');
await page.fill('[name="title"]', 'Buy groceries');
await page.click('button:has-text("Create")');
// Verify task appears
await expect(page.locator('text=Buy groceries')).toBeVisible();
// Complete the task
await page.click('[aria-label="Complete Buy groceries"]');
await expect(page.locator('text=Buy groceries')).toHaveCSS(
'text-decoration-line', 'line-through'
);
});
Test Anti-Patterns
| Anti-Pattern | Problem | Better Approach |
|---|---|---|
| Testing implementation details | Breaks on refactor | Test inputs/outputs |
| Snapshot everything | No one reviews snapshot diffs | Assert specific values |
| Shared mutable state | Tests pollute each other | Setup/teardown per test |
| Testing third-party code | Wastes time, not your bug | Mock the boundary |
| Skipping tests to pass CI | Hides real bugs | Fix or delete the test |
Using test.skip permanently |
Dead code | Remove or fix it |
| Overly broad assertions | Doesn't catch regressions | Be specific |
| No async error handling | Swallowed errors, false passes | Always await async tests |