Unit Testing with Jest

· 4 min read · Updated March 7, 2026 · beginner
jest testing unit-tests beginner

Jest is a zero-configuration testing framework developed by Meta (formerly Facebook). It ships with everything you need: test runner, assertion library, mocking capabilities, and code coverage tools. In this tutorial, you will learn how to write effective unit tests with Jest.

Installing Jest

Jest works in any JavaScript environment, Node.js, browsers, or React apps. The fastest way to add it to a project is via npm:

npm install --save-dev jest

Add a test script to your package.json:

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

Or run Jest directly with npx:

npx jest

Jest defaults to finding files matching these patterns: *.test.js, *.spec.js, or in a __tests__ folder.

Writing Your First Test

Jest uses describe blocks to group related tests and test (or it) functions for individual test cases:

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

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

module.exports = { add, subtract };
// math.test.js
const { add, subtract } = require('./math');

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

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

Run your tests:

npx jest

Output:

PASS  ./math.test.js
  Math functions
    ✓ adds two numbers correctly
    ✓ subtracts two numbers correctly

Understanding Matchers

Jest’s expect object provides matchers to assert values. Here are the most useful ones.

Equality Matchers

expect(value).toBe(5)          // Strict equality (===)
expect(value).toEqual(obj)    // Deep equality (for objects/arrays)
expect(value).toBeNull()       // Strict null check
expect(value).toBeUndefined()  // Strict undefined check
expect(value).toBeDefined()    // Opposite of toBeUndefined
expect(value).toBeTruthy()     // Truthy check
expect(value).toBeFalsy()      // Falsy check

Numeric Matchers

expect(num).toBeGreaterThan(10)   // >
expect(num).toBeGreaterThanOrEqual(10) // >=
expect(num).toBeLessThan(10)      // <
expect(num).toBeLessThanOrEqual(10) // <=
expect(num).toBeCloseTo(0.1, 5)   // Floating point comparison

String Matchers

expect(str).toMatch(/pattern/)    // Regex match
expect(str).toContain('子')       // Substring check

Array Matchers

expect(arr).toContain(item)       // Array contains item
expect(arr).toHaveLength(3)      // Array length check

Use .not to invert any matcher:

expect(result).not.toBeNull();
expect(items).not.toContain('bad');

Setup and Teardown

Often you need to run code before or after each test, or before and after all tests in a block:

describe('Database operations', () => {
  let db;

  // Run once before all tests
  beforeAll(async () => {
    db = await connectToDatabase();
  });

  // Run after all tests complete
  afterAll(async () => {
    await db.disconnect();
  });

  // Run before each individual test
  beforeEach(() => {
    db.clear();
  });

  // Run after each test
  afterEach(() => {
    db.resetMocks();
  });

  test('saves a user', async () => {
    const user = await db.saveUser({ name: 'Alice' });
    expect(user.id).toBeDefined();
  });
});

Testing Asynchronous Code

Jest supports multiple patterns for testing async code.

Using async/await

test('fetches user data', async () => {
  const user = await fetchUser(1);
  
  expect(user).toEqual({
    id: 1,
    name: 'Alice',
    email: 'alice@example.com'
  });
});

Using Promises

test('fetches user data', () => {
  return fetchUser(1).then(user => {
    expect(user.name).toBe('Alice');
  });
});

Using resolves/rejects

test('fetches user data', () => {
  return expect(fetchUser(1)).resolves.toHaveProperty('name', 'Alice');
});

test('handles errors', () => {
  return expect(fetchUser(999)).rejects.toThrow('User not found');
});

Mocking Functions

Jest makes it easy to mock functions, modules, and even timers.

Mocking a Single Function

const fetchData = require('./fetchData');

test('mocks a function', () => {
  const mockFn = jest.fn();
  mockFn.mockReturnValue(42);
  
  expect(mockFn()).toBe(42);
  expect(mockFn).toHaveBeenCalled();
  expect(mockFn).toHaveBeenCalledTimes(1);
});

Mocking Module Dependencies

When your code imports a module, you can mock the entire module:

// api.js
const axios = require('axios');

module.exports = {
  getUser: (id) => axios.get(`/users/${id}`)
};

// api.test.js
const api = require('./api');
const axios = require('axios');

jest.mock('axios');

test('fetches user successfully', async () => {
  axios.get.mockResolvedValue({
    data: { id: 1, name: 'Alice' }
  });
  
  const user = await api.getUser(1);
  
  expect(user.data.name).toBe('Alice');
  expect(axios.get).toHaveBeenCalledWith('/users/1');
});

Mock Implementations

const mockFn = jest.fn()
  .mockImplementationOnce(() => 'first call')
  .mockImplementationOnce(() => 'second call');

console.log(mockFn()); // 'first call'
console.log(mockFn()); // 'second call'
console.log(mockFn()); // undefined (fallback)

Mocking Timers

For code that uses setTimeout or setInterval, Jest can fake the timers:

// timer.js
function delayedCallback(callback) {
  setTimeout(() => {
    callback('done');
  }, 1000);
}

// timer.test.js
test('delayed callback', () => {
  jest.useFakeTimers();
  
  const callback = jest.fn();
  delayedCallback(callback);
  
  // Timer has not fired yet
  expect(callback).not.toHaveBeenCalled();
  
  // Fast-forward time
  jest.advanceTimersByTime(1000);
  
  // Now callback should have fired
  expect(callback).toHaveBeenCalledWith('done');
  
  jest.useRealTimers();
});

Test Organization Tips

  1. Follow the AAA pattern: Arrange (setup), Act (execute), Assert (verify)
  2. One expectation per test when possible, makes debugging easier
  3. Use descriptive test names: adds positive numbers not test1
  4. Keep tests isolated: each test should be independent
  5. Test edge cases: empty arrays, null values, negative numbers

Summary

You have learned the fundamentals of testing with Jest:

  • Installing and configuring Jest
  • Writing tests with describe and test
  • Using matchers like toBe, toEqual, toMatch
  • Setup and teardown with beforeEach, afterAll, etc.
  • Testing async code with async/await
  • Mocking functions, modules, and timers

In the next tutorial, we will explore Unit Testing with Vitest, which offers a Jest-compatible API with better performance and native ESM support.