JavaScript Testing Basics

Testing means writing code that automatically checks whether your other code works correctly. Instead of clicking through your app manually every time you make a change, tests run in seconds and catch broken functionality instantly. Good tests are the safety net that lets you refactor and add features with confidence.

Why Write Tests?

Imagine building a calculator. You add a new feature — currency conversion — and accidentally break the basic addition. Without tests, you might not notice until a user reports it. With tests, the broken addition test fails within seconds of your change.

Diagram: Test as a Safety Net

Without tests:
  Change code → manually click through app → miss bug → user reports it

With tests:
  Change code → tests run automatically → broken test fails instantly
  → fix it before it ever reaches users

Types of Tests

TypeTests WhatExample
Unit TestA single function or classDoes add(2, 3) return 5?
Integration TestMultiple parts working togetherDoes login + redirect work end to end?
End-to-End (E2E)Full user journey in a real browserCan a user sign up, log in, and place an order?

The Anatomy of a Test

Every test follows the AAA pattern: Arrange, Act, Assert.

Diagram: AAA Pattern

Arrange → set up the data and conditions
  Act    → run the function or action
    Assert → check that the result is what you expected

Example:
  Arrange: let a = 2, b = 3
  Act:     let result = add(a, b)
  Assert:  result should equal 5

Writing Tests Without a Framework

You can write basic tests in plain JavaScript to understand what a testing framework does behind the scenes.

// The function to test
function add(a, b) {
  return a + b;
}

// A simple test helper
function expect(value) {
  return {
    toBe(expected) {
      if (value === expected) {
        console.log("PASS ✓");
      } else {
        console.error("FAIL ✗ Expected:", expected, "Got:", value);
      }
    }
  };
}

// Tests
expect(add(2, 3)).toBe(5);     // PASS ✓
expect(add(0, 0)).toBe(0);     // PASS ✓
expect(add(-1, 1)).toBe(0);    // PASS ✓
expect(add(2, 3)).toBe(99);    // FAIL ✗ Expected: 99 Got: 5

Introduction to Jest

Jest is the most popular JavaScript testing framework. It provides everything you need: a test runner, assertion functions, and mocking tools — all in one package.

Install Jest

npm install --save-dev jest

Add to package.json

{
  "scripts": {
    "test": "jest"
  }
}

Run Tests

npm test

Writing Your First Jest Test

// math.js — the code to test
function add(a, b)      { return a + b; }
function subtract(a, b) { return a - b; }
function multiply(a, b) { return a * b; }

module.exports = { add, subtract, multiply };
// math.test.js — the test file
const { add, subtract, multiply } = require("./math");

test("add: 2 + 3 should equal 5", function() {
  expect(add(2, 3)).toBe(5);
});

test("subtract: 10 - 4 should equal 6", function() {
  expect(subtract(10, 4)).toBe(6);
});

test("multiply: 3 × 4 should equal 12", function() {
  expect(multiply(3, 4)).toBe(12);
});

Jest Output

 PASS  ./math.test.js
  ✓ add: 2 + 3 should equal 5 (2ms)
  ✓ subtract: 10 - 4 should equal 6 (0ms)
  ✓ multiply: 3 × 4 should equal 12 (0ms)

Tests: 3 passed, 3 total

Common Jest Matchers

Matchers are the assertion methods chained to expect().

// Equality
expect(1 + 1).toBe(2);                    // strict equal (===)
expect({ a: 1 }).toEqual({ a: 1 });       // deep equal (for objects/arrays)

// Truthiness
expect(true).toBeTruthy();
expect(null).toBeFalsy();
expect(null).toBeNull();
expect(undefined).toBeUndefined();

// Numbers
expect(10).toBeGreaterThan(5);
expect(3).toBeLessThan(10);
expect(3.14).toBeCloseTo(3.1, 1);         // floating point comparison

// Strings
expect("Hello World").toContain("World");
expect("test@mail.com").toMatch(/@/);      // regex match

// Arrays
expect([1, 2, 3]).toContain(2);
expect([1, 2, 3]).toHaveLength(3);

// Errors
expect(() => JSON.parse("bad")).toThrow();
expect(() => JSON.parse("bad")).toThrow(SyntaxError);

Grouping Tests with describe

Group related tests inside a describe block to organise your test file.

describe("Calculator functions", function() {
  describe("add", function() {
    test("adds two positive numbers", () => {
      expect(add(3, 4)).toBe(7);
    });
    test("adds negative and positive", () => {
      expect(add(-3, 3)).toBe(0);
    });
  });

  describe("multiply", function() {
    test("multiplies two numbers", () => {
      expect(multiply(3, 4)).toBe(12);
    });
    test("multiplies by zero", () => {
      expect(multiply(5, 0)).toBe(0);
    });
  });
});

Diagram: describe + test Structure

describe("Calculator functions")
  │
  ├─ describe("add")
  │     ├─ test("adds positives")
  │     └─ test("adds negatives")
  │
  └─ describe("multiply")
        ├─ test("multiplies two numbers")
        └─ test("multiplies by zero")

Testing Async Code

// Async function to test
async function fetchData(id) {
  let response = await fetch("https://jsonplaceholder.typicode.com/posts/" + id);
  return response.json();
}

// Test with async/await
test("fetches post with id 1", async function() {
  let post = await fetchData(1);
  expect(post.id).toBe(1);
  expect(post).toHaveProperty("title");
});

setup and Teardown

Run code before or after tests using beforeEach, afterEach, beforeAll, and afterAll.

let cart;

beforeEach(function() {
  // Reset cart before every test
  cart = [];
});

test("cart starts empty", function() {
  expect(cart).toHaveLength(0);
});

test("can add item to cart", function() {
  cart.push({ name: "Laptop", price: 50000 });
  expect(cart).toHaveLength(1);
  expect(cart[0].name).toBe("Laptop");
});

What Makes a Good Test

  • Tests one specific thing — not five things in a single test.
  • Has a clear, descriptive name that explains what it checks.
  • Does not depend on the order tests run.
  • Runs fast — unit tests should finish in milliseconds.
  • Fails clearly — when it breaks, you know exactly why.

Summary

Testing automatically verifies that your JavaScript code works as expected. Unit tests check individual functions, integration tests check combined parts, and end-to-end tests check full user flows. The AAA pattern — Arrange, Act, Assert — structures every test. Jest provides test(), expect(), and matchers like toBe, toEqual, and toThrow to write and run tests with a simple command. Write tests alongside your code — they catch bugs early and give you confidence to improve your codebase without fear of breaking things.

Leave a Comment

Your email address will not be published. Required fields are marked *