Elysia Tools

Browse tools, examples, visual explainers, and topic guides from one clear workspace.

Top Categories

Hubs

Language
ZHDERUPTENFRES

Elysia Tools

Navigation

Testing

Jest Testing Framework

Comprehensive Jest testing examples including unit tests, integration tests, mocking, async testing, and advanced patterns for JavaScript/TypeScript applications

Samples

Entries inside this sample collection

CodetextSimple

Jest Basic Setup and Configuration

Complete Jest project setup with configuration files, basic test structure, and common utilities

Difficulty

2/10

Estimated time

15 min

Tags

jest, configuration, setup, unit testing

Prerequisites

JavaScript ES6+, npm/yarn

text
// Jest Configuration Examples

// 1. jest.config.js - Basic configuration
module.exports = {
  // Test environment
  testEnvironment: 'jsdom',

  // Test file patterns
  testMatch: [
    '**/__tests__/**/*.+(ts|tsx|js)',
    '**/*.(test|spec).+(ts|tsx|js)'
  ],

  // Coverage configuration
  collectCoverage: true,
  collectCoverageFrom: [
    'src/**/*.(ts|tsx|js)',
    '!src/**/*.d.ts',
    '!src/index.ts'
  ],
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'html'],

  // Setup files
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],

  // Module path mapping
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '\.(css|less|scss|sass)$': 'identity-obj-proxy'
  },

  // Transform configuration
  transform: {
    '^.+\.(ts|tsx)$': 'ts-jest',
    '^.+\.(js|jsx)$': 'babel-jest'
  },

  // Test timeout
  testTimeout: 10000,

  // Verbose output
  verbose: true
};

// 2. package.json scripts
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:ci": "jest --ci --coverage --watchAll=false"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "@types/jest": "^29.5.8",
    "ts-jest": "^29.1.1",
    "babel-jest": "^29.7.0",
    "@babel/preset-env": "^7.23.6"
  }
}

// 3. src/setupTests.js - Global test setup
import '@testing-library/jest-dom';

// Mock global objects
global.fetch = jest.fn();

// Setup custom matchers
expect.extend({
  toBeWithinRange(received, floor, ceiling) {
    const pass = received >= floor && received <= ceiling;
    if (pass) {
      return {
        message: () =>
          `expected ${received} not to be within range ${floor} - ${ceiling}`,
        pass: true,
      };
    } else {
      return {
        message: () =>
          `expected ${received} to be within range ${floor} - ${ceiling}`,
        pass: false,
      };
    }
  },
});

// 4. Basic test file structure
// src/utils/math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => {
  if (b === 0) throw new Error('Division by zero');
  return a / b;
};

// src/utils/__tests__/math.test.js
import { add, subtract, multiply, divide } from '../math';

describe('Math Utilities', () => {
  describe('add', () => {
    test('should add two positive numbers', () => {
      expect(add(2, 3)).toBe(5);
    });

    test('should add negative numbers', () => {
      expect(add(-2, -3)).toBe(-5);
    });

    test('should handle zero', () => {
      expect(add(0, 5)).toBe(5);
      expect(add(5, 0)).toBe(5);
    });
  });

  describe('subtract', () => {
    test('should subtract two numbers', () => {
      expect(subtract(10, 4)).toBe(6);
    });
  });

  describe('multiply', () => {
    test('should multiply two numbers', () => {
      expect(multiply(3, 4)).toBe(12);
    });

    test('should handle multiplication by zero', () => {
      expect(multiply(5, 0)).toBe(0);
    });
  });

  describe('divide', () => {
    test('should divide two numbers', () => {
      expect(divide(10, 2)).toBe(5);
    });

    test('should throw error when dividing by zero', () => {
      expect(() => divide(10, 0)).toThrow('Division by zero');
    });
  });
});
CodetextIntermediate

Async and Promise Testing

Comprehensive examples of testing asynchronous code, promises, async/await, timers, and API calls with Jest

Difficulty

5/10

Estimated time

25 min

Tags

jest, async, promises, api testing

Prerequisites

JavaScript async/await, Promise basics

text
// Async and Promise Testing with Jest

// 1. Testing Promises
// src/api/userService.js
export const userService = {
  async getUser(id) {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      throw new Error('User not found');
    }
    return response.json();
  },

  async createUser(userData) {
    const response = await fetch('/api/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(userData),
    });
    return response.json();
  },

  getUsersWithDelay() {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]);
      }, 1000);
    });
  }
};

// src/api/__tests__/userService.test.js
import { userService } from '../userService';

// Mock fetch globally
global.fetch = jest.fn();

describe('UserService - Async Tests', () => {
  beforeEach(() => {
    fetch.mockClear();
  });

  describe('getUser', () => {
    test('should return user data when user exists', async () => {
      const mockUser = { id: 1, name: 'John Doe', email: 'john@example.com' };

      fetch.mockResolvedValueOnce({
        ok: true,
        json: async () => mockUser,
      });

      const result = await userService.getUser(1);

      expect(fetch).toHaveBeenCalledWith('/api/users/1');
      expect(result).toEqual(mockUser);
    });

    test('should throw error when user not found', async () => {
      fetch.mockResolvedValueOnce({
        ok: false,
      });

      await expect(userService.getUser(999)).rejects.toThrow('User not found');
    });

    // Using resolves matcher
    test('should work with resolves matcher', async () => {
      const mockUser = { id: 1, name: 'John' };
      fetch.mockResolvedValueOnce({
        ok: true,
        json: async () => mockUser,
      });

      await expect(userService.getUser(1)).resolves.toEqual(mockUser);
    });

    // Using rejects matcher
    test('should work with rejects matcher', async () => {
      fetch.mockResolvedValueOnce({
        ok: false,
      });

      await expect(userService.getUser(999)).rejects.toThrow('User not found');
    });
  });

  describe('createUser', () => {
    test('should create user successfully', async () => {
      const newUser = { name: 'New User', email: 'new@example.com' };
      const createdUser = { id: 2, ...newUser };

      fetch.mockResolvedValueOnce({
        ok: true,
        json: async () => createdUser,
      });

      const result = await userService.createUser(newUser);

      expect(fetch).toHaveBeenCalledWith('/api/users', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(newUser),
      });
      expect(result).toEqual(createdUser);
    });
  });
});

// 2. Testing with Timers
// src/utils/timer.js
export const timerUtils = {
  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  },

  async waitForCondition(conditionFn, timeout = 5000) {
    const startTime = Date.now();

    while (Date.now() - startTime < timeout) {
      if (await conditionFn()) {
        return true;
      }
      await new Promise(resolve => setTimeout(resolve, 100));
    }

    throw new Error('Condition not met within timeout');
  }
};

// src/utils/__tests__/timer.test.js
import { timerUtils } from '../timer';

describe('Timer Utils', () => {
  // Use fake timers to control setTimeout
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  test('delay should resolve after specified time', async () => {
    const delayPromise = timerUtils.delay(1000);

    // Promise should not be resolved yet
    let isResolved = false;
    delayPromise.then(() => {
      isResolved = true;
    });

    expect(isResolved).toBe(false);

    // Fast-forward time
    jest.advanceTimersByTime(1000);

    // Wait for the promise to resolve
    await Promise.resolve();
    expect(isResolved).toBe(true);
  });

  test('waitForCondition should work with fake timers', async () => {
    let conditionMet = false;

    // Mock condition that becomes true after 2 seconds
    const conditionFn = jest.fn(async () => {
      await timerUtils.delay(100);
      return conditionMet;
    });

    const waitForPromise = timerUtils.waitForCondition(conditionFn, 5000);

    // Fast-forward 1 second - condition should still be false
    jest.advanceTimersByTime(1100);
    await Promise.resolve();

    // Set condition to true and fast-forward more
    conditionMet = true;
    jest.advanceTimersByTime(200);
    await Promise.resolve();

    // Promise should now be resolved
    await expect(waitForPromise).resolves.toBe(true);
  });
});

// 3. Testing async callbacks and event handlers
// src/events/eventEmitter.js
export class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }

  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(callback => {
        // Simulate async callback
        setTimeout(() => callback(data), 0);
      });
    }
  }
}

// src/events/__tests__/eventEmitter.test.js
import { EventEmitter } from '../eventEmitter';

describe('EventEmitter - Async Callbacks', () => {
  test('should execute async callbacks', async () => {
    const emitter = new EventEmitter();
    const callback = jest.fn();

    emitter.on('test-event', callback);
    emitter.emit('test-event', { message: 'hello' });

    // Callback should not be called immediately due to setTimeout
    expect(callback).not.toHaveBeenCalled();

    // Wait for async callbacks to execute
    await new Promise(resolve => setTimeout(resolve, 0));

    expect(callback).toHaveBeenCalledWith({ message: 'hello' });
    expect(callback).toHaveBeenCalledTimes(1);
  });

  test('should handle multiple async callbacks', async () => {
    const emitter = new EventEmitter();
    const callback1 = jest.fn();
    const callback2 = jest.fn();

    emitter.on('test-event', callback1);
    emitter.on('test-event', callback2);
    emitter.emit('test-event', { data: 'test' });

    // Wait for all async callbacks
    await new Promise(resolve => setTimeout(resolve, 0));

    expect(callback1).toHaveBeenCalledWith({ data: 'test' });
    expect(callback2).toHaveBeenCalledWith({ data: 'test' });
  });
});
CodetextIntermediate

Mocking and Stubbing Strategies

Advanced mocking techniques including module mocking, function mocking, API mocking, and custom mock implementations

Difficulty

6/10

Estimated time

30 min

Tags

jest, mocking, testing patterns

Prerequisites

JavaScript modules, ES6 classes, async/await

text
// Advanced Mocking Strategies with Jest

// 1. Function Mocking
// src/utils/payment.js
export const paymentService = {
  processPayment: async (amount, cardNumber) => {
    // External API call
    const response = await fetch('/api/payments', {
      method: 'POST',
      body: JSON.stringify({ amount, cardNumber })
    });
    return response.json();
  },

  validateCard: (cardNumber) => {
    // Complex validation logic
    return /^[0-9]{16}$/.test(cardNumber.replace(/\s/g, ''));
  }
};

// src/utils/__tests__/payment.test.js
import { paymentService } from '../payment';

describe('Payment Service - Mocking Examples', () => {
  beforeEach(() => {
    // Reset all mocks before each test
    jest.clearAllMocks();
  });

  test('should mock processPayment function', async () => {
    // Mock the entire function
    const mockProcessPayment = jest.spyOn(paymentService, 'processPayment');
    mockProcessPayment.mockResolvedValue({
      success: true,
      transactionId: '12345'
    });

    const result = await paymentService.processPayment(100, '4111111111111111');

    expect(mockProcessPayment).toHaveBeenCalledWith(100, '4111111111111111');
    expect(result).toEqual({ success: true, transactionId: '12345' });
  });

  test('should mock card validation', () => {
    const mockValidateCard = jest.spyOn(paymentService, 'validateCard');
    mockValidateCard.mockReturnValue(true);

    const isValid = paymentService.validateCard('invalid-card');

    expect(mockValidateCard).toHaveBeenCalledWith('invalid-card');
    expect(isValid).toBe(true);
  });
});

// 2. Module Mocking
// src/services/api.js
import axios from 'axios';

export const apiService = {
  async getUsers() {
    const response = await axios.get('/api/users');
    return response.data;
  },

  async createUser(userData) {
    const response = await axios.post('/api/users', userData);
    return response.data;
  }
};

// src/services/__tests__/api.test.js
import axios from 'axios';
import { apiService } from '../api';

// Mock the entire axios module
jest.mock('axios');

describe('API Service - Module Mocking', () => {
  test('should fetch users successfully', async () => {
    const mockUsers = [
      { id: 1, name: 'John' },
      { id: 2, name: 'Jane' }
    ];

    // Mock axios.get implementation
    axios.get.mockResolvedValue({
      data: mockUsers
    });

    const users = await apiService.getUsers();

    expect(axios.get).toHaveBeenCalledWith('/api/users');
    expect(users).toEqual(mockUsers);
  });

  test('should handle API errors', async () => {
    const errorMessage = 'Network Error';
    axios.get.mockRejectedValue(new Error(errorMessage));

    await expect(apiService.getUsers()).rejects.toThrow(errorMessage);
  });

  test('should create user successfully', async () => {
    const newUser = { name: 'New User', email: 'new@example.com' };
    const createdUser = { id: 3, ...newUser };

    axios.post.mockResolvedValue({
      data: createdUser
    });

    const result = await apiService.createUser(newUser);

    expect(axios.post).toHaveBeenCalledWith('/api/users', newUser);
    expect(result).toEqual(createdUser);
  });
});

// 3. Mock Implementations
// src/utils/database.js
export const database = {
  async connect() {
    // Simulate database connection
    await new Promise(resolve => setTimeout(resolve, 100));
    return { connected: true };
  },

  async query(sql, params = []) {
    // Simulate database query
    console.log(`Executing: ${sql}`, params);
    await new Promise(resolve => setTimeout(resolve, 50));
    return { rows: [], rowCount: 0 };
  }
};

// src/utils/__tests__/database.test.js
import { database } from '../database';

describe('Database - Mock Implementations', () => {
  test('should mock database connection', async () => {
    // Create a mock implementation
    const mockConnect = jest.spyOn(database, 'connect');
    mockConnect.mockImplementation(async () => {
      console.log('Mock connection established');
      return { connected: true, mock: true };
    });

    const result = await database.connect();

    expect(mockConnect).toHaveBeenCalled();
    expect(result).toEqual({ connected: true, mock: true });
  });

  test('should mock database query with custom implementation', async () => {
    const mockQuery = jest.spyOn(database, 'query');
    mockQuery.mockImplementation(async (sql, params) => {
      console.log(`Mock query: ${sql}`, params);

      // Return different results based on SQL
      if (sql.includes('SELECT')) {
        return {
          rows: [{ id: 1, name: 'Mock User' }],
          rowCount: 1
        };
      }

      return { rows: [], rowCount: 0 };
    });

    const selectResult = await database.query('SELECT * FROM users');
    const insertResult = await database.query('INSERT INTO users (name) VALUES (?)', ['Test']);

    expect(mockQuery).toHaveBeenCalledTimes(2);
    expect(selectResult.rows).toHaveLength(1);
    expect(insertResult.rows).toHaveLength(0);
  });
});

// 4. Manual Mocks
// __mocks__/localStorage.js
export const localStorageMock = {
  getItem: jest.fn(),
  setItem: jest.fn(),
  removeItem: jest.fn(),
  clear: jest.fn(),
  length: 0,
  key: jest.fn(),
};

export default localStorageMock;

// src/utils/storage.js
export const storageService = {
  saveUser(user) {
    localStorage.setItem('user', JSON.stringify(user));
  },

  getUser() {
    const userStr = localStorage.getItem('user');
    return userStr ? JSON.parse(userStr) : null;
  },

  removeUser() {
    localStorage.removeItem('user');
  }
};

// src/utils/__tests__/storage.test.js
import localStorageMock from '../../__mocks__/localStorage';

// Mock the localStorage module
jest.mock('../localStorage', () => localStorageMock);

import { storageService } from '../storage';

describe('Storage Service - Manual Mocks', () => {
  beforeEach(() => {
    // Clear all mock calls before each test
    localStorageMock.getItem.mockClear();
    localStorageMock.setItem.mockClear();
    localStorageMock.removeItem.mockClear();
  });

  test('should save user to localStorage', () => {
    const user = { id: 1, name: 'John' };

    storageService.saveUser(user);

    expect(localStorageMock.setItem).toHaveBeenCalledWith(
      'user',
      JSON.stringify(user)
    );
  });

  test('should get user from localStorage', () => {
    const user = { id: 1, name: 'John' };

    // Mock the return value
    localStorageMock.getItem.mockReturnValue(JSON.stringify(user));

    const result = storageService.getUser();

    expect(localStorageMock.getItem).toHaveBeenCalledWith('user');
    expect(result).toEqual(user);
  });

  test('should return null when no user in localStorage', () => {
    localStorageMock.getItem.mockReturnValue(null);

    const result = storageService.getUser();

    expect(result).toBeNull();
  });

  test('should remove user from localStorage', () => {
    storageService.removeUser();

    expect(localStorageMock.removeItem).toHaveBeenCalledWith('user');
  });
});
CodetextIntermediate

React Component Testing with Jest

Complete React testing examples using Jest and React Testing Library, including component rendering, events, hooks, and integration tests

Difficulty

7/10

Estimated time

35 min

Tags

jest, react, testing-library, component testing

Prerequisites

React, JavaScript ES6+, JSX

text
// React Component Testing with Jest and React Testing Library

// 1. Basic Component Testing
// src/components/Button.jsx
import React from 'react';
import PropTypes from 'prop-types';

const Button = ({ children, onClick, disabled = false, variant = 'primary' }) => {
  const buttonClass = `button button--${variant}`;

  return (
    <button
      className={buttonClass}
      onClick={onClick}
      disabled={disabled}
      data-testid="button"
    >
      {children}
    </button>
  );
};

Button.propTypes = {
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func,
  disabled: PropTypes.bool,
  variant: PropTypes.oneOf(['primary', 'secondary', 'danger'])
};

export default Button;

// src/components/__tests__/Button.test.jsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Button from '../Button';

describe('Button Component', () => {
  test('renders with text content', () => {
    render(<Button>Click me</Button>);

    const button = screen.getByTestId('button');
    expect(button).toBeInTheDocument();
    expect(button).toHaveTextContent('Click me');
  });

  test('applies correct variant class', () => {
    render(<Button variant="secondary">Secondary Button</Button>);

    const button = screen.getByTestId('button');
    expect(button).toHaveClass('button', 'button--secondary');
  });

  test('calls onClick handler when clicked', async () => {
    const user = userEvent.setup();
    const handleClick = jest.fn();

    render(<Button onClick={handleClick}>Click me</Button>);

    const button = screen.getByTestId('button');
    await user.click(button);

    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  test('does not call onClick when disabled', async () => {
    const user = userEvent.setup();
    const handleClick = jest.fn();

    render(<Button onClick={handleClick} disabled>Disabled Button</Button>);

    const button = screen.getByTestId('button');
    expect(button).toBeDisabled();

    await user.click(button);
    expect(handleClick).not.toHaveBeenCalled();
  });

  test('has correct accessibility attributes', () => {
    render(<Button disabled>Submit</Button>);

    const button = screen.getByTestId('button');
    expect(button).toHaveAttribute('disabled');
  });
});

// 2. Complex Component Testing
// src/components/UserProfile.jsx
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';

const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);

        if (!response.ok) {
          throw new Error('User not found');
        }

        const userData = await response.json();
        setUser(userData);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    if (userId) {
      fetchUser();
    }
  }, [userId]);

  if (loading) {
    return <div data-testid="loading">Loading user profile...</div>;
  }

  if (error) {
    return <div data-testid="error">Error: {error}</div>;
  }

  if (!user) {
    return <div data-testid="no-user">No user selected</div>;
  }

  return (
    <div data-testid="user-profile" className="user-profile">
      <div className="user-avatar">
        <img src={user.avatar} alt={`${user.name}'s avatar`} />
      </div>
      <div className="user-info">
        <h2>{user.name}</h2>
        <p>{user.email}</p>
        <p>{user.bio}</p>
      </div>
    </div>
  );
};

UserProfile.propTypes = {
  userId: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
};

export default UserProfile;

// src/components/__tests__/UserProfile.test.jsx
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import UserProfile from '../UserProfile';

// Mock fetch globally
global.fetch = jest.fn();

describe('UserProfile Component', () => {
  beforeEach(() => {
    fetch.mockClear();
  });

  test('shows loading state initially', () => {
    fetch.mockImplementation(() => new Promise(() => {})); // Never resolves

    render(<UserProfile userId="1" />);

    expect(screen.getByTestId('loading')).toBeInTheDocument();
    expect(screen.getByTestId('loading')).toHaveTextContent('Loading user profile...');
  });

  test('displays user data after successful fetch', async () => {
    const mockUser = {
      id: 1,
      name: 'John Doe',
      email: 'john@example.com',
      bio: 'Software developer',
      avatar: 'https://example.com/avatar.jpg'
    };

    fetch.mockResolvedValueOnce({
      ok: true,
      json: async () => mockUser,
    });

    render(<UserProfile userId="1" />);

    // Wait for the user profile to appear
    await waitFor(() => {
      expect(screen.getByTestId('user-profile')).toBeInTheDocument();
    });

    expect(screen.getByText('John Doe')).toBeInTheDocument();
    expect(screen.getByText('john@example.com')).toBeInTheDocument();
    expect(screen.getByText('Software developer')).toBeInTheDocument();

    const avatar = screen.getByAltText("John Doe's avatar");
    expect(avatar).toHaveAttribute('src', 'https://example.com/avatar.jpg');
  });

  test('displays error message when fetch fails', async () => {
    fetch.mockResolvedValueOnce({
      ok: false,
      status: 404,
    });

    render(<UserProfile userId="999" />);

    await waitFor(() => {
      expect(screen.getByTestId('error')).toBeInTheDocument();
    });

    expect(screen.getByTestId('error')).toHaveTextContent('Error: User not found');
  });

  test('displays no user message when no userId provided', () => {
    render(<UserProfile />);

    expect(screen.getByTestId('no-user')).toBeInTheDocument();
    expect(screen.getByTestId('no-user')).toHaveTextContent('No user selected');
    expect(fetch).not.toHaveBeenCalled();
  });

  test('refetches user when userId changes', async () => {
    const mockUser1 = { id: 1, name: 'User 1', email: 'user1@example.com' };
    const mockUser2 = { id: 2, name: 'User 2', email: 'user2@example.com' };

    fetch
      .mockResolvedValueOnce({
        ok: true,
        json: async () => mockUser1,
      })
      .mockResolvedValueOnce({
        ok: true,
        json: async () => mockUser2,
      });

    const { rerender } = render(<UserProfile userId="1" />);

    await waitFor(() => {
      expect(screen.getByText('User 1')).toBeInTheDocument();
    });

    rerender(<UserProfile userId="2" />);

    await waitFor(() => {
      expect(screen.getByText('User 2')).toBeInTheDocument();
    });

    expect(fetch).toHaveBeenCalledTimes(2);
    expect(fetch).toHaveBeenNthCalledWith(1, '/api/users/1');
    expect(fetch).toHaveBeenNthCalledWith(2, '/api/users/2');
  });
});

// 3. Custom Hook Testing
// src/hooks/useCounter.js
import { useState, useCallback } from 'react';

export const useCounter = (initialValue = 0) => {
  const [count, setCount] = useState(initialValue);

  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  const decrement = useCallback(() => {
    setCount(prev => prev - 1);
  }, []);

  const reset = useCallback(() => {
    setCount(initialValue);
  }, [initialValue]);

  return {
    count,
    increment,
    decrement,
    reset
  };
};

// src/hooks/__tests__/useCounter.test.js
import { renderHook, act } from '@testing-library/react';
import { useCounter } from '../useCounter';

describe('useCounter Hook', () => {
  test('should initialize with default value', () => {
    const { result } = renderHook(() => useCounter());

    expect(result.current.count).toBe(0);
    expect(typeof result.current.increment).toBe('function');
    expect(typeof result.current.decrement).toBe('function');
    expect(typeof result.current.reset).toBe('function');
  });

  test('should initialize with custom value', () => {
    const { result } = renderHook(() => useCounter(5));

    expect(result.current.count).toBe(5);
  });

  test('should increment count', () => {
    const { result } = renderHook(() => useCounter(0));

    act(() => {
      result.current.increment();
    });

    expect(result.current.count).toBe(1);
  });

  test('should decrement count', () => {
    const { result } = renderHook(() => useCounter(10));

    act(() => {
      result.current.decrement();
    });

    expect(result.current.count).toBe(9);
  });

  test('should reset count to initial value', () => {
    const { result } = renderHook(() => useCounter(7));

    act(() => {
      result.current.increment();
      result.current.increment();
    });
    expect(result.current.count).toBe(9);

    act(() => {
      result.current.reset();
    });
    expect(result.current.count).toBe(7);
  });

  test('should handle multiple increments', () => {
    const { result } = renderHook(() => useCounter());

    act(() => {
      result.current.increment();
      result.current.increment();
      result.current.increment();
    });

    expect(result.current.count).toBe(3);
  });
});

// 4. Integration Testing with Multiple Components
// src/components/UserForm.jsx
import React, { useState } from 'react';
import PropTypes from 'prop-types';

const UserForm = ({ onSubmit, initialUser = {} }) => {
  const [user, setUser] = useState({
    name: '',
    email: '',
    ...initialUser
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(user);
  };

  const handleChange = (e) => {
    const { name, value } = e.target;
    setUser(prev => ({ ...prev, [name]: value }));
  };

  return (
    <form data-testid="user-form" onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          id="name"
          name="name"
          type="text"
          value={user.name}
          onChange={handleChange}
          data-testid="name-input"
        />
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input
          id="email"
          name="email"
          type="email"
          value={user.email}
          onChange={handleChange}
          data-testid="email-input"
        />
      </div>
      <button type="submit" data-testid="submit-button">Submit</button>
    </form>
  );
};

UserForm.propTypes = {
  onSubmit: PropTypes.func.isRequired,
  initialUser: PropTypes.object
};

export default UserForm;

// src/components/__tests__/UserForm.integration.test.jsx
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UserForm from '../UserForm';
import UserProfile from '../UserProfile';

// Mock fetch for UserProfile
global.fetch = jest.fn();

describe('User Management Integration', () => {
  beforeEach(() => {
    fetch.mockClear();
  });

  test('should create user and display in profile', async () => {
    const createdUser = {
      id: 1,
      name: 'John Doe',
      email: 'john@example.com',
      bio: 'Software developer',
      avatar: 'https://example.com/avatar.jpg'
    };

    // Mock the API calls
    fetch
      .mockResolvedValueOnce({
        ok: true,
        json: async () => createdUser,
      })
      .mockResolvedValueOnce({
        ok: true,
        json: async () => createdUser,
      });

    // Test component that combines form and profile
    const UserManagement = () => {
      const [currentUser, setCurrentUser] = React.useState(null);

      const handleUserSubmit = async (userData) => {
        // Simulate API call
        const response = await fetch('/api/users', {
          method: 'POST',
          body: JSON.stringify(userData)
        });
        const user = await response.json();
        setCurrentUser(user);
      };

      return (
        <div>
          <UserForm onSubmit={handleUserSubmit} />
          {currentUser && <UserProfile userId={currentUser.id} />}
        </div>
      );
    };

    render(<UserManagement />);

    // Fill out the form
    const nameInput = screen.getByTestId('name-input');
    const emailInput = screen.getByTestId('email-input');
    const submitButton = screen.getByTestId('submit-button');

    await userEvent.type(nameInput, 'John Doe');
    await userEvent.type(emailInput, 'john@example.com');
    await userEvent.click(submitButton);

    // Wait for the user profile to appear
    await waitFor(() => {
      expect(screen.getByTestId('user-profile')).toBeInTheDocument();
    });

    // Verify the user data is displayed
    expect(screen.getByText('John Doe')).toBeInTheDocument();
    expect(screen.getByText('john@example.com')).toBeInTheDocument();
  });
});
CodetextComplex

Test-Driven Development (TDD) Patterns

Complete TDD workflow examples with red-green-refactor cycles, test organization, and best practices for building robust applications

Difficulty

8/10

Estimated time

45 min

Tags

jest, tdd, testing patterns, best practices

Prerequisites

JavaScript advanced, React basics, Testing concepts

text
// Test-Driven Development (TDD) with Jest

// 1. TDD Example: Building a String Calculator
// Test first, then implementation

// src/calculator/__tests__/stringCalculator.test.js
describe('String Calculator (TDD Example)', () => {
  let stringCalculator;

  beforeEach(() => {
    // Import the implementation we're building
    stringCalculator = require('../stringCalculator').default;
  });

  test('should return 0 for empty string', () => {
    expect(stringCalculator.add("")).toBe(0);
  });

  test('should return number for single number string', () => {
    expect(stringCalculator.add("1")).toBe(1);
    expect(stringCalculator.add("5")).toBe(5);
  });

  test('should return sum for two numbers separated by comma', () => {
    expect(stringCalculator.add("1,2")).toBe(3);
    expect(stringCalculator.add("10,20")).toBe(30);
  });

  test('should return sum for multiple numbers', () => {
    expect(stringCalculator.add("1,2,3,4,5")).toBe(15);
    expect(stringCalculator.add("10,20,30")).toBe(60);
  });

  test('should handle new lines as delimiters', () => {
    expect(stringCalculator.add("1\n2,3")).toBe(6);
    expect(stringCalculator.add("4\n5\n6")).toBe(15);
  });

  test('should support custom delimiters', () => {
    expect(stringCalculator.add("//;\n1;2;3")).toBe(6);
    expect(stringCalculator.add("//|\n4|5|6")).toBe(15);
  });

  test('should throw error for negative numbers', () => {
    expect(() => stringCalculator.add("1,-2,3")).toThrow("negative numbers not allowed: -2");
    expect(() => stringCalculator.add("-1,2,-3")).toThrow("negative numbers not allowed: -1,-3");
  });

  test('should ignore numbers greater than 1000', () => {
    expect(stringCalculator.add("2,1001,3")).toBe(5);
    expect(stringCalculator.add("1000,1001,1002")).toBe(1000);
  });

  test('should support delimiters of any length', () => {
    expect(stringCalculator.add("//[***]\n1***2***3")).toBe(6);
    expect(stringCalculator.add("//[delimiter]\n4delimiter5delimiter6")).toBe(15);
  });

  test('should support multiple delimiters', () => {
    expect(stringCalculator.add("//[*][%]\n1*2%3")).toBe(6);
    expect(stringCalculator.add("//[**][%%]\n4**5%%6")).toBe(15);
  });
});

// Implementation built through TDD
// src/calculator/stringCalculator.js
class StringCalculator {
  add(numbers) {
    if (!numbers) return 0;

    let delimiter = /,|\n/;
    let numbersString = numbers;

    // Check for custom delimiters
    if (numbers.startsWith("//")) {
      const delimiterMatch = numbers.match(/^//(.+)\n(.+)$/);
      if (delimiterMatch) {
        const [, delimiterPart, numberString] = delimiterMatch;

        // Handle multiple delimiters like [*][%]
        const delimiterMatches = delimiterPart.match(/\[([^\]]+)\]/g);
        if (delimiterMatches) {
          const delimiters = delimiterMatches.map(match =>
            match.slice(1, -1).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
          );
          delimiter = new RegExp(delimiters.join('|'));
        } else {
          // Single delimiter
          const escapedDelimiter = delimiterPart.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
          delimiter = new RegExp(escapedDelimiter);
        }

        numbersString = numberString;
      }
    }

    const numberArray = numbersString.split(delimiter);
    const validNumbers = numberArray
      .map(num => parseInt(num.trim()))
      .filter(num => !isNaN(num))
      .filter(num => num <= 1000);

    const negativeNumbers = validNumbers.filter(num => num < 0);
    if (negativeNumbers.length > 0) {
      throw new Error(`negative numbers not allowed: ${negativeNumbers.join(',')}`);
    }

    return validNumbers.reduce((sum, num) => sum + num, 0);
  }
}

export default StringCalculator;

// 2. TDD Example: Building a Todo List Application
// src/todo/__tests__/todoManager.test.js
describe('Todo Manager (TDD Example)', () => {
  let todoManager;

  beforeEach(() => {
    todoManager = require('../todoManager').default;
    todoManager.clearTodos(); // Reset state for each test
  });

  describe('Adding todos', () => {
    test('should add a new todo', () => {
      const todo = todoManager.addTodo('Buy milk');

      expect(todo).toMatchObject({
        id: expect.any(Number),
        text: 'Buy milk',
        completed: false,
        createdAt: expect.any(Date)
      });
    });

    test('should throw error for empty todo text', () => {
      expect(() => todoManager.addTodo('')).toThrow('Todo text cannot be empty');
      expect(() => todoManager.addTodo('   ')).toThrow('Todo text cannot be empty');
    });

    test('should generate unique IDs', () => {
      const todo1 = todoManager.addTodo('First task');
      const todo2 = todoManager.addTodo('Second task');

      expect(todo1.id).not.toBe(todo2.id);
    });
  });

  describe('Retrieving todos', () => {
    test('should return empty array initially', () => {
      const todos = todoManager.getAllTodos();
      expect(todos).toEqual([]);
    });

    test('should return all todos', () => {
      todoManager.addTodo('Task 1');
      todoManager.addTodo('Task 2');

      const todos = todoManager.getAllTodos();
      expect(todos).toHaveLength(2);
      expect(todos[0].text).toBe('Task 1');
      expect(todos[1].text).toBe('Task 2');
    });

    test('should return only active todos', () => {
      todoManager.addTodo('Active task');
      const completedTodo = todoManager.addTodo('Completed task');
      todoManager.toggleTodo(completedTodo.id);

      const activeTodos = todoManager.getActiveTodos();
      expect(activeTodos).toHaveLength(1);
      expect(activeTodos[0].text).toBe('Active task');
    });

    test('should return only completed todos', () => {
      const activeTodo = todoManager.addTodo('Active task');
      const completedTodo = todoManager.addTodo('Completed task');
      todoManager.toggleTodo(completedTodo.id);

      const completedTodos = todoManager.getCompletedTodos();
      expect(completedTodos).toHaveLength(1);
      expect(completedTodos[0].text).toBe('Completed task');
    });
  });

  describe('Updating todos', () => {
    test('should toggle todo completion', () => {
      const todo = todoManager.addTodo('Test task');
      expect(todo.completed).toBe(false);

      const updatedTodo = todoManager.toggleTodo(todo.id);
      expect(updatedTodo.completed).toBe(true);
      expect(updatedTodo.completedAt).toBeInstanceOf(Date);

      const toggledBack = todoManager.toggleTodo(todo.id);
      expect(toggledBack.completed).toBe(false);
      expect(toggledBack.completedAt).toBeNull();
    });

    test('should update todo text', () => {
      const todo = todoManager.addTodo('Original text');
      const updatedTodo = todoManager.updateTodoText(todo.id, 'Updated text');

      expect(updatedTodo.text).toBe('Updated text');
      expect(updatedTodo.updatedAt).toBeInstanceOf(Date);
    });

    test('should throw error when updating non-existent todo', () => {
      expect(() => todoManager.toggleTodo(999)).toThrow('Todo not found');
      expect(() => todoManager.updateTodoText(999, 'New text')).toThrow('Todo not found');
    });
  });

  describe('Deleting todos', () => {
    test('should delete todo', () => {
      const todo = todoManager.addTodo('Task to delete');
      expect(todoManager.getAllTodos()).toHaveLength(1);

      todoManager.deleteTodo(todo.id);
      expect(todoManager.getAllTodos()).toHaveLength(0);
    });

    test('should throw error when deleting non-existent todo', () => {
      expect(() => todoManager.deleteTodo(999)).toThrow('Todo not found');
    });
  });

  describe('Todo statistics', () => {
    test('should return correct statistics', () => {
      const todo1 = todoManager.addTodo('Task 1');
      const todo2 = todoManager.addTodo('Task 2');
      todoManager.toggleTodo(todo2.id);

      const stats = todoManager.getStatistics();

      expect(stats).toMatchObject({
        total: 2,
        active: 1,
        completed: 1,
        completionRate: 50
      });
    });

    test('should handle empty statistics', () => {
      const stats = todoManager.getStatistics();

      expect(stats).toMatchObject({
        total: 0,
        active: 0,
        completed: 0,
        completionRate: 0
      });
    });
  });
});

// Implementation built through TDD
// src/todo/todoManager.js
class TodoManager {
  constructor() {
    this.todos = [];
    this.nextId = 1;
  }

  addTodo(text) {
    if (!text || text.trim() === '') {
      throw new Error('Todo text cannot be empty');
    }

    const todo = {
      id: this.nextId++,
      text: text.trim(),
      completed: false,
      createdAt: new Date(),
      completedAt: null,
      updatedAt: null
    };

    this.todos.push(todo);
    return todo;
  }

  getAllTodos() {
    return [...this.todos];
  }

  getActiveTodos() {
    return this.todos.filter(todo => !todo.completed);
  }

  getCompletedTodos() {
    return this.todos.filter(todo => todo.completed);
  }

  toggleTodo(id) {
    const todo = this.todos.find(t => t.id === id);
    if (!todo) {
      throw new Error('Todo not found');
    }

    todo.completed = !todo.completed;
    todo.completedAt = todo.completed ? new Date() : null;
    todo.updatedAt = new Date();

    return todo;
  }

  updateTodoText(id, newText) {
    if (!newText || newText.trim() === '') {
      throw new Error('Todo text cannot be empty');
    }

    const todo = this.todos.find(t => t.id === id);
    if (!todo) {
      throw new Error('Todo not found');
    }

    todo.text = newText.trim();
    todo.updatedAt = new Date();

    return todo;
  }

  deleteTodo(id) {
    const todoIndex = this.todos.findIndex(t => t.id === id);
    if (todoIndex === -1) {
      throw new Error('Todo not found');
    }

    this.todos.splice(todoIndex, 1);
  }

  getStatistics() {
    const total = this.todos.length;
    const completed = this.todos.filter(todo => todo.completed).length;
    const active = total - completed;
    const completionRate = total > 0 ? Math.round((completed / total) * 100) : 0;

    return {
      total,
      active,
      completed,
      completionRate
    };
  }

  clearTodos() {
    this.todos = [];
    this.nextId = 1;
  }
}

const todoManager = new TodoManager();
export default todoManager;

// 3. Test Organization and Structure
// tests/setup.js
import { configure } from '@testing-library/react';

// Configure Testing Library
configure({ testIdAttribute: 'data-testid' });

// Global test utilities
global.testUtils = {
  createMockUser: (overrides = {}) => ({
    id: 1,
    name: 'Test User',
    email: 'test@example.com',
    ...overrides
  }),

  createMockApiResponse: (data, options = {}) => ({
    data,
    status: 200,
    statusText: 'OK',
    headers: {},
    config: {},
    ...options
  }),

  waitForAsync: () => new Promise(resolve => setTimeout(resolve, 0))
};

// Custom matchers
expect.extend({
  toBeValidEmail(received) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    const pass = emailRegex.test(received);

    if (pass) {
      return {
        message: () => `expected ${received} not to be a valid email`,
        pass: true,
      };
    } else {
      return {
        message: () => `expected ${received} to be a valid email`,
        pass: false,
      };
    }
  },

  toBeWithinRange(received, floor, ceiling) {
    const pass = received >= floor && received <= ceiling;
    return {
      message: () =>
        pass
          ? `expected ${received} not to be within range ${floor} - ${ceiling}`
          : `expected ${received} to be within range ${floor} - ${ceiling}`,
      pass,
    };
  }
});

// 4. Best Practices and Patterns
// tests/utils/testUtils.js
export class TestBuilder {
  constructor() {
    this.setupFns = [];
    this.teardownFns = [];
  }

  beforeEach(fn) {
    this.setupFns.push(fn);
    return this;
  }

  afterEach(fn) {
    this.teardownFns.push(fn);
    return this;
  }

  build(testFn) {
    return async () => {
      try {
        // Run setup functions
        for (const setupFn of this.setupFns) {
          await setupFn();
        }

        // Run the actual test
        await testFn();
      } finally {
        // Run teardown functions
        for (const teardownFn of this.teardownFns) {
          await teardownFn();
        }
      }
    };
  }
}

// Usage example:
const testBuilder = new TestBuilder()
  .beforeEach(() => {
    // Setup code
  })
  .afterEach(() => {
    // Cleanup code
  });

test('my test', testBuilder.build(() => {
  // Test implementation
}));

// 5. Continuous Integration Testing Script
// scripts/test-ci.js
const { execSync } = require('child_process');

function runCommand(command, description) {
  console.log(`🧪 ${description}...`);
  try {
    execSync(command, { stdio: 'inherit' });
    console.log(`✅ ${description} - PASSED`);
    return true;
  } catch (error) {
    console.log(`❌ ${description} - FAILED`);
    return false;
  }
}

async function runAllTests() {
  const tests = [
    {
      command: 'jest --passWithNoTests --testPathPattern=__tests__',
      description: 'Unit Tests'
    },
    {
      command: 'jest --passWithNoTests --testPathPattern=integration',
      description: 'Integration Tests'
    },
    {
      command: 'jest --coverage --passWithNoTests',
      description: 'Coverage Tests'
    },
    {
      command: 'eslint src/ --ext .js,.jsx,.ts,.tsx',
      description: 'Linting'
    }
  ];

  let allPassed = true;
  for (const test of tests) {
    const passed = runCommand(test.command, test.description);
    if (!passed) {
      allPassed = false;
      // Continue running other tests for full feedback
    }
  }

  if (allPassed) {
    console.log('🎉 All tests passed!');
    process.exit(0);
  } else {
    console.log('💥 Some tests failed!');
    process.exit(1);
  }
}

runAllTests().catch(console.error);

Tools

Tools frequently paired with this sample

Related

Related sample collections

Related

Related workflows

Hub

Deliberate Text Corruption and Mutation for Testing

Break clean text on purpose — filter, duplicate, and reverse sentences and words, inject random letters and symbols, shuffle lines and words, and prove every mutation stays controlled for parser stress tests, OCR rehearsal, and language experiments.

Hub

cURL API Request Conversion and Testing

Convert a cURL request into code or HAR, test it safely, and explain the HTTP status without losing headers, body, or auth context.

Hub

API Contract Definition, Schema Validation, and Change Testing

Define an API contract, validate schemas and captured payloads, detect compatibility risks, and record test acceptance.

Hub

Regex Testing, Visualization, and Safety Workflow

Build reliable regular expressions by learning syntax, testing matches, visualizing structure, previewing replacements, benchmarking alternatives, and scanning for ReDoS risk.

Home
Tools3212
Workbench
Samples593
Visualizations471
Viewers103
Hubs239
Tags333
Images, Audio & Video657

Image, audio, and video processing, conversion, and optimization tools

Math, Date & Finance647

Calculators, numerics, date logic, statistics, and finance tools

Design & Color298

Color, layout, graphics, visual styling, and design helper tools

Text & Writing209

Writing, cleanup, formatting, extraction, and text analysis tools

Developer & Web193

Developer utilities, networking, web debugging, and automation helpers

Conversion & Encoding175

Format, file, archive, unit, and encoding conversion tools

Lead Generation Quiz Launch and Contact Handoff
Memorial Drafts: Family Review and Print Handoff
Private Habit Records: Backup, Restore and Sharing Review
Article to X and LinkedIn Drafts: Fact Review and Handoff
Markdown Interactive Deck Review and Offline Handoff

© 2026 Elysia Tools. All rights reserved.

DocsPricingAboutTerms of ServicePrivacy PolicyCookiesContact

Development

Docstring Extractor

Extract and parse documentation from JavaScript/TypeScript, Python, and Java source code

Development

AI Regex Explainer

Use AI to break down regular expressions into readable explanations

Data Processing

Array Extractor

Extract elements of specific types from arrays (numbers, strings, booleans, objects, etc.)

Data Processing

Array Filter

Filter out elements of specific types from arrays (remove numbers, strings, booleans, etc.)

Media

Batch Audio Rename

Batch rename audio files using patterns, text replacement, numbering, and case conversion. Returns renamed files as a ZIP download.

Media

Audio Script Timing Estimator

Estimate how long a script will take to read aloud, with optional calibration from a recorded sample.

Development

Glob to Regex

Convert file matching patterns (Glob) to regular expressions

Design

Add Texture Effect

Add various texture overlays to images with customizable blend modes and intensity

Testing

Playwright E2E Testing Framework

Comprehensive Playwright testing examples including cross-browser automation, API testing, mobile testing, visual regression, and advanced E2E patterns for modern web applications

Testing

Test Pyramid Examples - Testing Strategy Guide

Comprehensive test pyramid implementation examples including unit tests, integration tests, E2E tests, test organization, and strategic testing patterns for balanced software quality assurance

Testing

JUnit 5 Testing Framework

Comprehensive JUnit 5 testing examples including unit tests, integration tests, parameterized tests, mocking with Mockito, and advanced testing patterns for Java applications

Testing

Pytest Testing Framework

Comprehensive Pytest testing examples including unit tests, integration tests, fixtures, parametrization, mocking, and advanced testing patterns for Python applications

Testing Frameworks

Vitest Samples

Modern JavaScript testing framework examples with Vite integration, featuring unit tests, component testing, and modern testing patterns

Testing

BDD with Cucumber - Behavior Driven Development

Comprehensive Cucumber BDD examples including feature files, step definitions, data tables, hooks, and advanced BDD patterns for collaborative development