JavaScript Testing & Quality Assurance
Learn testing strategies, frameworks, and best practices for ensuring JavaScript code quality and reliability
50 min•By Priygop Team•Last 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