Back to catalog

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