Skip to main content
Course/Module 9/Topic 2 of 5Advanced

JavaScript Testing & Quality Assurance

Learn testing strategies, frameworks, and best practices for ensuring JavaScript code quality and reliability

50 minBy Priygop TeamLast updated: Feb 2026

Testing Fundamentals

Testing is crucial for building reliable JavaScript applications. Modern testing frameworks provide powerful tools for unit testing, integration testing, and end-to-end testing to ensure code quality and prevent regressions.

Jest Testing Framework

Example
// Jest configuration (jest.config.js)
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
  moduleNameMapping: {
    '\.(css|less|scss|sass)$': 'identity-obj-proxy',
    '\.(gif|ttf|eot|svg)$': '<rootDir>/__mocks__/fileMock.js'
  },
  collectCoverageFrom: [
    'src/**/*.{js,jsx}',
    '!src/index.js',
    '!src/reportWebVitals.js'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
};

// Basic Jest test
// math.test.js
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

describe('Math functions', () => {
  test('adds two numbers correctly', () => {
    expect(add(2, 3)).toBe(5);
    expect(add(-1, 1)).toBe(0);
    expect(add(0, 0)).toBe(0);
  });

  test('subtracts two numbers correctly', () => {
    expect(subtract(5, 3)).toBe(2);
    expect(subtract(1, 1)).toBe(0);
    expect(subtract(0, 5)).toBe(-5);
  });
});

// Async testing
// api.test.js
async function fetchUser(id) {
  const response = await fetch(`https://api.example.com/users/${id}`);
  return response.json();
}

describe('API functions', () => {
  test('fetches user successfully', async () => {
    const user = await fetchUser(1);
    expect(user).toHaveProperty('id');
    expect(user).toHaveProperty('name');
    expect(user.id).toBe(1);
  });

  test('handles API errors', async () => {
    await expect(fetchUser(999)).rejects.toThrow();
  });
});

// Mocking
// userService.test.js
import { getUser, createUser } from './userService';

// Mock the fetch function
global.fetch = jest.fn();

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

  test('gets user successfully', async () => {
    const mockUser = { id: 1, name: 'John Doe' };
    fetch.mockResolvedValueOnce({
      ok: true,
      json: async () => mockUser
    });

    const user = await getUser(1);
    expect(user).toEqual(mockUser);
    expect(fetch).toHaveBeenCalledWith('https://api.example.com/users/1');
  });

  test('creates user successfully', async () => {
    const newUser = { name: 'Jane Doe', email: 'jane@example.com' };
    const createdUser = { id: 2, ...newUser };
    
    fetch.mockResolvedValueOnce({
      ok: true,
      json: async () => createdUser
    });

    const result = await createUser(newUser);
    expect(result).toEqual(createdUser);
  });
});

React Testing

Example
// React Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import App from './App';

// Component testing
describe('App Component', () => {
  test('renders welcome message', () => {
    render(<App />);
    expect(screen.getByText(/welcome/i)).toBeInTheDocument();
  });

  test('handles button click', () => {
    render(<App />);
    const button = screen.getByRole('button', { name: /click me/i });
    fireEvent.click(button);
    expect(screen.getByText(/clicked/i)).toBeInTheDocument();
  });

  test('handles form submission', async () => {
    const user = userEvent.setup();
    render(<App />);
    
    const input = screen.getByLabelText(/name/i);
    const submitButton = screen.getByRole('button', { name: /submit/i });
    
    await user.type(input, 'John Doe');
    await user.click(submitButton);
    
    await waitFor(() => {
      expect(screen.getByText(/hello john doe/i)).toBeInTheDocument();
    });
  });
});

// Custom hook testing
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

describe('useCounter Hook', () => {
  test('initializes with default value', () => {
    const { result } = renderHook(() => useCounter());
    expect(result.current.count).toBe(0);
  });

  test('initializes with custom value', () => {
    const { result } = renderHook(() => useCounter(10));
    expect(result.current.count).toBe(10);
  });

  test('increments counter', () => {
    const { result } = renderHook(() => useCounter());
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });

  test('decrements counter', () => {
    const { result } = renderHook(() => useCounter(5));
    
    act(() => {
      result.current.decrement();
    });
    
    expect(result.current.count).toBe(4);
  });
});

// Integration testing
import { render, screen } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

const renderWithRouter = (component) => {
  return render(
    <BrowserRouter>
      {component}
    </BrowserRouter>
  );
};

describe('App Integration', () => {
  test('navigates between pages', () => {
    renderWithRouter(<App />);
    
    const homeLink = screen.getByText(/home/i);
    const aboutLink = screen.getByText(/about/i);
    
    fireEvent.click(aboutLink);
    expect(screen.getByText(/about page/i)).toBeInTheDocument();
    
    fireEvent.click(homeLink);
    expect(screen.getByText(/home page/i)).toBeInTheDocument();
  });
});

End-to-End Testing

Example
// Cypress E2E Testing
// cypress/e2e/todo.cy.js
describe('Todo App', () => {
  beforeEach(() => {
    cy.visit('http://localhost:3000');
  });

  it('should add a new todo', () => {
    const todoText = 'Learn Cypress testing';
    
    cy.get('[data-testid="todo-input"]')
      .type(todoText);
    
    cy.get('[data-testid="add-todo-button"]')
      .click();
    
    cy.get('[data-testid="todo-list"]')
      .should('contain', todoText);
  });

  it('should mark todo as complete', () => {
    // Add a todo first
    cy.get('[data-testid="todo-input"]')
      .type('Test todo');
    cy.get('[data-testid="add-todo-button"]')
      .click();
    
    // Mark as complete
    cy.get('[data-testid="todo-checkbox"]')
      .first()
      .check();
    
    cy.get('[data-testid="todo-item"]')
      .first()
      .should('have.class', 'completed');
  });

  it('should delete a todo', () => {
    // Add a todo first
    cy.get('[data-testid="todo-input"]')
      .type('Delete me');
    cy.get('[data-testid="add-todo-button"]')
      .click();
    
    // Delete the todo
    cy.get('[data-testid="delete-todo-button"]')
      .first()
      .click();
    
    cy.get('[data-testid="todo-list"]')
      .should('not.contain', 'Delete me');
  });
});

// Playwright E2E Testing
// tests/todo.spec.js
import { test, expect } from '@playwright/test';

test.describe('Todo Application', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('http://localhost:3000');
  });

  test('should add and complete todos', async ({ page }) => {
    // Add a new todo
    await page.fill('[data-testid="todo-input"]', 'Learn Playwright');
    await page.click('[data-testid="add-todo-button"]');
    
    // Verify todo was added
    await expect(page.locator('[data-testid="todo-list"]'))
      .toContainText('Learn Playwright');
    
    // Mark as complete
    await page.check('[data-testid="todo-checkbox"]');
    await expect(page.locator('[data-testid="todo-item"]').first())
      .toHaveClass(/completed/);
  });

  test('should handle multiple todos', async ({ page }) => {
    const todos = ['First todo', 'Second todo', 'Third todo'];
    
    for (const todo of todos) {
      await page.fill('[data-testid="todo-input"]', todo);
      await page.click('[data-testid="add-todo-button"]');
    }
    
    await expect(page.locator('[data-testid="todo-item"]'))
      .toHaveCount(3);
  });
});

// Test configuration
// playwright.config.js
module.exports = {
  testDir: './tests',
  timeout: 30000,
  expect: {
    timeout: 5000
  },
  use: {
    headless: false,
    viewport: { width: 1280, height: 720 },
    ignoreHTTPSErrors: true,
    video: 'on-first-retry',
    screenshot: 'only-on-failure'
  },
  projects: [
    {
      name: 'chromium',
      use: { browserName: 'chromium' }
    },
    {
      name: 'firefox',
      use: { browserName: 'firefox' }
    },
    {
      name: 'webkit',
      use: { browserName: 'webkit' }
    }
  ]
};

Practice Exercise: Testing

Example
// Exercise: Build a complete testing suite
// 1. Create a simple calculator module
// src/calculator.js
export class Calculator {
  add(a, b) {
    return a + b;
  }
  
  subtract(a, b) {
    return a - b;
  }
  
  multiply(a, b) {
    return a * b;
  }
  
  divide(a, b) {
    if (b === 0) {
      throw new Error('Division by zero');
    }
    return a / b;
  }
  
  power(base, exponent) {
    return Math.pow(base, exponent);
  }
  
  sqrt(number) {
    if (number < 0) {
      throw new Error('Cannot calculate square root of negative number');
    }
    return Math.sqrt(number);
  }
}

// 2. Create unit tests
// tests/calculator.test.js
import { Calculator } from '../src/calculator.js';

describe('Calculator', () => {
  let calculator;
  
  beforeEach(() => {
    calculator = new Calculator();
  });
  
  describe('Basic Operations', () => {
    test('adds two positive numbers', () => {
      expect(calculator.add(2, 3)).toBe(5);
    });
    
    test('adds negative numbers', () => {
      expect(calculator.add(-1, -2)).toBe(-3);
    });
    
    test('subtracts two numbers', () => {
      expect(calculator.subtract(5, 3)).toBe(2);
    });
    
    test('multiplies two numbers', () => {
      expect(calculator.multiply(4, 5)).toBe(20);
    });
    
    test('divides two numbers', () => {
      expect(calculator.divide(10, 2)).toBe(5);
    });
  });
  
  describe('Edge Cases', () => {
    test('throws error on division by zero', () => {
      expect(() => calculator.divide(5, 0)).toThrow('Division by zero');
    });
    
    test('throws error on negative square root', () => {
      expect(() => calculator.sqrt(-4)).toThrow('Cannot calculate square root of negative number');
    });
    
    test('handles zero operations', () => {
      expect(calculator.add(0, 0)).toBe(0);
      expect(calculator.multiply(5, 0)).toBe(0);
    });
  });
  
  describe('Advanced Operations', () => {
    test('calculates power correctly', () => {
      expect(calculator.power(2, 3)).toBe(8);
      expect(calculator.power(5, 0)).toBe(1);
    });
    
    test('calculates square root correctly', () => {
      expect(calculator.sqrt(16)).toBe(4);
      expect(calculator.sqrt(0)).toBe(0);
    });
  });
});

// 3. Create integration tests
// tests/calculator.integration.test.js
import { Calculator } from '../src/calculator.js';

describe('Calculator Integration', () => {
  let calculator;
  
  beforeEach(() => {
    calculator = new Calculator();
  });
  
  test('performs complex calculation chain', () => {
    const result = calculator
      .add(10, 5)        // 15
      .multiply(2)       // 30
      .subtract(5)       // 25
      .divide(5);        // 5
    
    expect(result).toBe(5);
  });
  
  test('handles multiple operations', () => {
    const a = calculator.add(3, 4);     // 7
    const b = calculator.multiply(2, 3); // 6
    const c = calculator.subtract(a, b); // 1
    
    expect(c).toBe(1);
  });
});

// 4. Create performance tests
// tests/calculator.performance.test.js
import { Calculator } from '../src/calculator.js';

describe('Calculator Performance', () => {
  let calculator;
  
  beforeEach(() => {
    calculator = new Calculator();
  });
  
  test('performs 1000 operations quickly', () => {
    const startTime = performance.now();
    
    for (let i = 0; i < 1000; i++) {
      calculator.add(i, i + 1);
    }
    
    const endTime = performance.now();
    const duration = endTime - startTime;
    
    expect(duration).toBeLessThan(100); // Should complete in less than 100ms
  });
});

// 5. Run tests with coverage
// npm test -- --coverage

// 6. Generate test report
// npm test -- --coverage --coverageReporters=html
Chat on WhatsApp
Priygop - Leading Professional Development Platform | Expert Courses & Interview Prep