React Jest Testing

Master JavaScript testing with Jest framework

🃏 What is Jest?

Jest is a JavaScript testing framework created by Facebook. It works seamlessly with React and provides features like mocking, snapshots, and code coverage. Jest makes testing simple and enjoyable with zero configuration.


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

// sum.test.js
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
                                    

Result:

✅ PASS: Test passes because 1 + 2 equals 3

Jest Core Features

Matchers

Assert expected values

expect(value).toBe(3);
expect(value).toEqual({a: 1});
🎭

Mocks

Replace functions with test doubles

const mockFn = jest.fn();
mockFn.mockReturnValue(42);
📸

Snapshots

Capture component output

expect(component)
  .toMatchSnapshot();
📊

Coverage

Track tested code percentage

jest --coverage

🔹 Basic Jest Test Structure

Jest tests use describe blocks to group related tests and test/it blocks for individual test cases. This structure keeps tests organized and readable.

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

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

export function multiply(a, b) {
  return a * b;
}
// math.test.js
import { add, subtract, multiply } from './math';

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

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

  test('multiplies two numbers', () => {
    expect(multiply(2, 3)).toBe(6);
    expect(multiply(-2, 3)).toBe(-6);
  });
  
});

Output:

✅ PASS: Math operations (3 tests)

🔹 Common Jest Matchers

Matchers let you test values in different ways. Jest provides many built-in matchers for common testing scenarios like equality, truthiness, and comparisons.

describe('Jest Matchers', () => {
  
  test('equality matchers', () => {
    expect(2 + 2).toBe(4);              // Strict equality
    expect({ a: 1 }).toEqual({ a: 1 }); // Deep equality
  });

  test('truthiness matchers', () => {
    expect(true).toBeTruthy();
    expect(false).toBeFalsy();
    expect(null).toBeNull();
    expect(undefined).toBeUndefined();
  });

  test('number matchers', () => {
    expect(10).toBeGreaterThan(5);
    expect(5).toBeLessThan(10);
    expect(0.1 + 0.2).toBeCloseTo(0.3); // Floating point
  });

  test('string matchers', () => {
    expect('Hello World').toMatch(/World/);
    expect('testing').toContain('test');
  });

  test('array matchers', () => {
    const fruits = ['apple', 'banana', 'orange'];
    expect(fruits).toContain('banana');
    expect(fruits).toHaveLength(3);
  });

  test('exception matchers', () => {
    const throwError = () => { throw new Error('Oops!'); };
    expect(throwError).toThrow();
    expect(throwError).toThrow('Oops!');
  });
  
});

Output:

✅ PASS: All matcher tests pass

🔹 Mocking Functions

Mock functions let you test code that depends on external functions or APIs. You can control their behavior and verify how they're called.

// api.js
export function fetchUser(id) {
  return fetch(`/api/users/${id}`).then(res => res.json());
}

export function greetUser(id, callback) {
  fetchUser(id).then(user => {
    callback(`Hello, ${user.name}!`);
  });
}
// api.test.js
import { greetUser, fetchUser } from './api';

// Mock the fetchUser function
jest.mock('./api', () => ({
  ...jest.requireActual('./api'),
  fetchUser: jest.fn()
}));

describe('Mocking Functions', () => {
  
  test('calls callback with greeting', async () => {
    // Setup mock return value
    fetchUser.mockResolvedValue({ name: 'John' });
    
    // Create mock callback
    const mockCallback = jest.fn();
    
    // Call function
    await greetUser(1, mockCallback);
    
    // Verify callback was called correctly
    expect(mockCallback).toHaveBeenCalledWith('Hello, John!');
    expect(mockCallback).toHaveBeenCalledTimes(1);
  });

  test('tracks function calls', () => {
    const mockFn = jest.fn(x => x * 2);
    
    mockFn(2);
    mockFn(3);
    
    // Check how many times called
    expect(mockFn).toHaveBeenCalledTimes(2);
    
    // Check what arguments were passed
    expect(mockFn).toHaveBeenCalledWith(2);
    expect(mockFn).toHaveBeenCalledWith(3);
    
    // Check return values
    expect(mockFn.mock.results[0].value).toBe(4);
    expect(mockFn.mock.results[1].value).toBe(6);
  });
  
});

Output:

✅ PASS: Mock functions work as expected

🔹 Setup and Teardown

Use setup and teardown functions to prepare test environments and clean up after tests. This ensures tests don't affect each other.

describe('Database Tests', () => {
  let database;

  // Runs before all tests in this describe block
  beforeAll(() => {
    database = connectToDatabase();
    console.log('Database connected');
  });

  // Runs after all tests in this describe block
  afterAll(() => {
    database.disconnect();
    console.log('Database disconnected');
  });

  // Runs before each test
  beforeEach(() => {
    database.clear();
    console.log('Database cleared');
  });

  // Runs after each test
  afterEach(() => {
    console.log('Test completed');
  });

  test('adds user to database', () => {
    database.addUser({ name: 'John' });
    expect(database.getUsers()).toHaveLength(1);
  });

  test('removes user from database', () => {
    database.addUser({ name: 'John' });
    database.removeUser('John');
    expect(database.getUsers()).toHaveLength(0);
  });
  
});

Output:

Database connected → Test 1 → Test 2 → Database disconnected

🔹 Snapshot Testing

Snapshot tests capture component output and compare it to saved snapshots. They're great for detecting unexpected UI changes in React components.

// Button.jsx
import React from 'react';

function Button({ variant, children }) {
  return (
    <button className={`btn btn-${variant}`}>
      {children}
    </button>
  );
}

export default Button;
// Button.test.jsx
import React from 'react';
import renderer from 'react-test-renderer';
import Button from './Button';

describe('Button Snapshots', () => {
  
  test('renders primary button', () => {
    const tree = renderer
      .create(<Button variant="primary">Click Me</Button>)
      .toJSON();
    
    expect(tree).toMatchSnapshot();
  });

  test('renders secondary button', () => {
    const tree = renderer
      .create(<Button variant="secondary">Cancel</Button>)
      .toJSON();
    
    expect(tree).toMatchSnapshot();
  });
  
});

Output:

✅ 2 snapshots written. Future runs will compare against these snapshots.

🔹 Testing Async Code

Jest provides multiple ways to test asynchronous code like promises and async/await. Always return promises or use async/await in tests.

// api.js
export function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ data: 'Hello World' });
    }, 100);
  });
}

export async function getUserData(id) {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}
// api.test.js
import { fetchData, getUserData } from './api';

describe('Async Tests', () => {
  
  // Method 1: Return promise
  test('fetches data with promise', () => {
    return fetchData().then(data => {
      expect(data.data).toBe('Hello World');
    });
  });

  // Method 2: Async/await
  test('fetches data with async/await', async () => {
    const data = await fetchData();
    expect(data.data).toBe('Hello World');
  });

  // Method 3: resolves matcher
  test('promise resolves correctly', () => {
    return expect(fetchData()).resolves.toEqual({
      data: 'Hello World'
    });
  });

  // Testing rejected promises
  test('handles errors', async () => {
    const failingFetch = () => Promise.reject('Error!');
    await expect(failingFetch()).rejects.toMatch('Error!');
  });
  
});

Output:

✅ PASS: All async tests complete successfully

🔹 Running Jest Tests

Jest provides various commands to run tests in different modes. Use these commands to fit your development workflow.

Common Jest Commands:

  • npm test - Run all tests
  • npm test -- --watch - Run tests in watch mode
  • npm test -- --coverage - Generate coverage report
  • npm test Button - Run tests matching "Button"
  • npm test -- --verbose - Show detailed test results
# Run all tests
npm test

# Watch mode - reruns tests on file changes
npm test -- --watch

# Coverage report
npm test -- --coverage

# Run specific test file
npm test Button.test.js

# Update snapshots
npm test -- -u

🧠 Test Your Knowledge

Which matcher checks for deep equality of objects?