testing-best-practices
This repository contains a complete **Cursor (Claude AI)** infrastructure that provides **auto-activating skills**, **specialized agents**, and **powerful commands** to help you build better applications faster.
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- Updated: 2025-11-22
Download the latest version (v1.0.0) from this page.
SKILL.md
# Testing Best Practices
**Skill Version:** 1.0.0
**Stack:** Jest, React Testing Library, TypeScript
**Purpose:** Production-ready testing patterns for Next.js applications
## Overview
This skill provides comprehensive testing patterns including unit tests, integration tests, component testing, and Firebase mocking strategies.
## When to Use This Skill
Load this skill when working on:
- Writing unit tests
- Testing React components
- Integration testing
- Mocking Firebase services
- Test organization
- Coverage reporting
## Quick Reference
### Test Types
1. **Unit Tests** - Individual functions and utilities
2. **Component Tests** - React components in isolation
3. **Integration Tests** - Multiple components working together
4. **E2E Tests** - Full user flows
## Core Guidelines
### 1. Jest Configuration
```javascript
// jest.config.js
const nextJest = require('next/jest');
const createJestConfig = nextJest({
// Path to Next.js app
dir: './',
});
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: {
'^@/(.*)#39;: '<rootDir>/src/$1',
},
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.stories.{js,jsx,ts,tsx}',
'!src/**/__tests__/**',
],
testMatch: [
'**/__tests__/**/*.(test|spec).(ts|tsx|js)',
'**/*.(test|spec).(ts|tsx|js)',
],
};
module.exports = createJestConfig(customJestConfig);
```
```javascript
// jest.setup.js
import '@testing-library/jest-dom';
// Mock Next.js router
jest.mock('next/navigation', () => ({
useRouter() {
return {
push: jest.fn(),
replace: jest.fn(),
prefetch: jest.fn(),
};
},
useSearchParams() {
return new URLSearchParams();
},
usePathname() {
return '';
},
}));
// Mock Firebase
jest.mock('firebase/app', () => ({
initializeApp: jest.fn(),
getApps: jest.fn(() => []),
}));
```
### 2. Unit Test Patterns
```typescript
// lib/__tests__/validation.test.ts
import {
isValidEmail,
isValidPhone,
sanitizeString,
validatePassword,
} from '../validation';
describe('validation utilities', () => {
describe('isValidEmail', () => {
it('should validate correct email formats', () => {
expect(isValidEmail('user@example.com')).toBe(true);
expect(isValidEmail('user.name@example.co.uk')).toBe(true);
});
it('should reject invalid email formats', () => {
expect(isValidEmail('invalid')).toBe(false);
expect(isValidEmail('user@')).toBe(false);
expect(isValidEmail('@example.com')).toBe(false);
});
it('should handle edge cases', () => {
expect(isValidEmail('')).toBe(false);
expect(isValidEmail(null as any)).toBe(false);
expect(isValidEmail(undefined as any)).toBe(false);
});
});
describe('validatePassword', () => {
it('should accept strong passwords', () => {
const result = validatePassword('StrongP@ssw0rd');
expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should reject weak passwords', () => {
const result = validatePassword('weak');
expect(result.isValid).toBe(false);
expect(result.errors.length).toBeGreaterThan(0);
});
it('should provide specific error messages', () => {
const result = validatePassword('password');
expect(result.errors).toContain('Password must contain at least one uppercase letter');
expect(result.errors).toContain('Password must contain at least one number');
});
});
describe('sanitizeString', () => {
it('should remove dangerous characters', () => {
expect(sanitizeString('<script>alert("xss")</script>')).not.toContain('<script>');
expect(sanitizeString('normal text')).toBe('normal text');
});
it('should handle edge cases', () => {
expect(sanitizeString('')).toBe('');
expect(sanitizeString(null as any)).toBe('');
expect(sanitizeString(undefined as any)).toBe('');
});
});
});
```
### 3. Component Test Patterns
```typescript
// components/__tests__/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '../Button';
describe('Button component', () => {
it('should render button with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('should call onClick handler when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('should be disabled when disabled prop is true', () => {
render(<Button disabled>Click me</Button>);
expect(screen.getByText('Click me')).toBeDisabled();
});
it('should show loading state', () => {
render(<Button loading>Click me</Button>);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('should not call onClick when disabled', () => {
const handleClick = jest.fn();
render(<Button disabled onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).not.toHaveBeenCalled();
});
});
```
### 4. Form Testing Pattern
```typescript
// components/__tests__/LoginForm.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from '../LoginForm';
describe('LoginForm', () => {
it('should render form fields', () => {
render(<LoginForm />);
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /sign in/i })).toBeInTheDocument();
});
it('should validate required fields', async () => {
render(<LoginForm />);
fireEvent.click(screen.getByRole('button', { name: /sign in/i }));
await waitFor(() => {
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
expect(screen.getByText(/password is required/i)).toBeInTheDocument();
});
});
it('should validate email format', async () => {
const user = userEvent.setup();
render(<LoginForm />);
const emailInput = screen.getByLabelText(/email/i);
await user.type(emailInput, 'invalid-email');
await user.tab();
await waitFor(() => {
expect(screen.getByText(/invalid email/i)).toBeInTheDocument();
});
});
it('should submit form with valid data', async () => {
const handleSubmit = jest.fn();
const user = userEvent.setup();
render(<LoginForm onSubmit={handleSubmit} />);
await user.type(screen.getByLabelText(/email/i), 'user@example.com');
await user.type(screen.getByLabelText(/password/i), 'password123');
await user.click(screen.getByRole('button', { name: /sign in/i }));
await waitFor(() => {
expect(handleSubmit).toHaveBeenCalledWith({
email: 'user@example.com',
password: 'password123',
});
});
});
});
```
### 5. Integration Test Pattern
```typescript
// app/__tests__/user-flow.integration.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { AppProviders } from '@/contexts';
import UserProfile from '@/app/profile/page';
// Mock Firebase
jest.mock('@/lib/firebase/auth', () => ({
getCurrentUser: jest.fn(() => ({
uid: 'test-user-123',
email: 'test@example.com',
displayName: 'Test User',
})),
}));
jest.mock('@/lib/firebase/firestore', () => ({
getUserData: jest.fn(() => Promise.resolve({
id: 'test-user-123',
name: 'Test User',
email: 'test@example.com',
})),
updateUserData: jest.fn(() => Promise.resolve()),
}));
describe('User Profile Flow', () => {
it('should load and display user data', async () => {
render(
<AppProviders>
<UserProfile />
</AppProviders>
);
await waitFor(() => {
expect(screen.getByText('Test User')).toBeInTheDocument();
expect(screen.getByText('test@example.com')).toBeInTheDocument();
});
});
it('should allow user to update profile', async () => {
const user = userEvent.setup();
const { updateUserData } = require('@/lib/firebase/firestore');
render(
<AppProviders>
<UserProfile />
</AppProviders>
);
await waitFor(() => {
expect(screen.getByText('Test User')).toBeInTheDocument();
});
// Click edit button
await user.click(screen.getByRole('button', { name: /edit/i }));
// Update name
const nameInput = screen.getByLabelText(/name/i);
await user.clear(nameInput);
await user.type(nameInput, 'Updated Name');
// Save
await user.click(screen.getByRole('button', { name: /save/i }));
await waitFor(() => {
expect(updateUserData).toHaveBeenCalledWith('test-user-123', {
name: 'Updated Name',
});
});
});
});
```
### 6. Firebase Mocking
```typescript
// __mocks__/firebase.ts
export const mockFirestore = {
collection: jest.fn(),
doc: jest.fn(),
getDoc: jest.fn(),
getDocs: jest.fn(),
addDoc: jest.fn(),
updateDoc: jest.fn(),
deleteDoc: jest.fn(),
query: jest.fn(),
where: jest.fn(),
orderBy: jest.fn(),
};
export const mockAuth = {
signInWithEmailAndPassword: jest.fn(),
signOut: jest.fn(),
onAuthStateChanged: jest.fn(),
currentUser: { uid: 'test-user', email: 'test@example.com' },
};
export const mockStorage = {
ref: jest.fn(),
uploadBytes: jest.fn(),
getDownloadURL: jest.fn(),
deleteObject: jest.fn(),
};
// Usage in tests
import { mockFirestore } from '@/__mocks__/firebase';
describe('User Service', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should fetch user data', async () => {
mockFirestore.getDoc.mockResolvedValue({
exists: () => true,
data: () => ({ name: 'Test User', email: 'test@example.com' }),
});
const user = await getUserById('user-123');
expect(user.name).toBe('Test User');
});
});
```
### 7. Test Organization
```
src/
├── lib/
│ ├── validation.ts
│ └── __tests__/
│ └── validation.test.ts
├── components/
│ ├── Button.tsx
│ └── __tests__/
│ └── Button.test.tsx
├── app/
│ ├── profile/
│ │ ├── page.tsx
│ │ └── __tests__/
│ └── profile.integration.test.tsx
```
### 8. Coverage Configuration
```json
// package.json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage --maxWorkers=2"
},
"jest": {
"coverageThreshold": {
"global": {
"branches": 70,
"functions": 70,
"lines": 70,
"statements": 70
}
}
}
}
```
## Best Practices
### Do's ✅
- **Test behavior, not implementation** details
- **Use descriptive test names** (what, when, expected)
- **Follow AAA pattern** (Arrange, Act, Assert)
- **Mock external dependencies** (APIs, Firebase, etc.)
- **Test edge cases** (null, undefined, empty, etc.)
- **Use data-testid sparingly** (prefer accessible queries)
- **Clean up** after each test
- **Test user interactions** (clicks, typing, etc.)
### Don'ts ❌
- **Don't test implementation** details
- **Don't test third-party libraries**
- **Don't write brittle tests** that break with UI changes
- **Don't skip error cases**
- **Don't test internal state** directly
- **Don't forget async waits**
- **Don't mock everything** (test what matters)
## Testing Checklist
- [ ] Unit tests for utilities
- [ ] Component tests for UI
- [ ] Integration tests for flows
- [ ] Error cases covered
- [ ] Edge cases tested
- [ ] Async operations handled
- [ ] Mocks properly configured
- [ ] Coverage meets threshold
## Common Patterns
### Testing Hooks
```typescript
import { renderHook, act } from '@testing-library/react';
import { useCounter } from '../useCounter';
test('should increment counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
```
### Testing Async Operations
```typescript
it('should load data', async () => {
render(<DataComponent />);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText(/data loaded/i)).toBeInTheDocument();
});
});
```
### Testing Errors
```typescript
it('should display error message', async () => {
// Mock API to return error
mockApi.get.mockRejectedValue(new Error('Failed'));
render(<Component />);
await waitFor(() => {
expect(screen.getByText(/failed to load/i)).toBeInTheDocument();
});
});
```
---
**Last Updated:** 2025-01-10
**Maintained By:** Development Team