Jest
02 / 03

Mocking & Advanced

Mocking & Advanced

jest.fn() — Mock Functions

const mockFn = jest.fn();

// Set return value
mockFn.mockReturnValue(42);
mockFn.mockReturnValueOnce(10).mockReturnValueOnce(20);

// Async
mockFn.mockResolvedValue({ id: 1, name: 'Alice' });
mockFn.mockRejectedValue(new Error('Failed'));

// Implementation
mockFn.mockImplementation((x: number) => x * 2);
mockFn.mockImplementationOnce(() => 'first call');

// Assertions on mock
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(3);
expect(mockFn).toHaveBeenCalledWith('arg1', 42);
expect(mockFn).toHaveBeenLastCalledWith('last-arg');
expect(mockFn).toHaveBeenNthCalledWith(2, 'second-call-arg');

// Access calls
console.log(mockFn.mock.calls);        // [[arg1, arg2], [arg3], ...]
console.log(mockFn.mock.results);      // [{type: 'return', value: 42}, ...]
console.log(mockFn.mock.instances);    // if called as constructor

// Reset / clear
mockFn.mockClear();     // clear calls, instances, results (keep implementation)
mockFn.mockReset();     // clear + remove return values/implementations
mockFn.mockRestore();   // restore original (only for jest.spyOn)

jest.mock() — Module Mocking

// Auto-mock entire module
jest.mock('../services/emailService');
import { sendEmail } from '../services/emailService';
const mockedSendEmail = sendEmail as jest.MockedFunction<typeof sendEmail>;
mockedSendEmail.mockResolvedValue(undefined);

// Manual mock with factory
jest.mock('../db', () => ({
  findUser: jest.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
  createUser: jest.fn().mockResolvedValue({ id: 2 }),
}));

// Partial mock — keep real implementations for some exports
jest.mock('../utils', () => ({
  ...jest.requireActual('../utils'),  // keep real implementations
  generateId: jest.fn().mockReturnValue('test-id'),
}));

// Mock default export
jest.mock('../config', () => ({
  default: { apiUrl: 'http://test-api', timeout: 1000 },
}));

// ES module default export
jest.mock('../logger', () => ({
  __esModule: true,
  default: {
    info: jest.fn(),
    error: jest.fn(),
  },
}));

jest.spyOn() — Spy on Methods

// Spy on existing method (can still call original)
const spy = jest.spyOn(userService, 'findById');
spy.mockResolvedValue({ id: 1, name: 'Alice' });

expect(spy).toHaveBeenCalledWith(1);
spy.mockRestore();  // restore original implementation

// Spy without changing behavior
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

// Spy on property
jest.spyOn(Date, 'now').mockReturnValue(1700000000000);

// Mock timers
jest.useFakeTimers();
jest.setSystemTime(new Date('2024-01-01'));

setTimeout(() => callback(), 1000);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();

jest.useRealTimers();

Testing React Components

// @testing-library/react
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserCard } from './UserCard';

describe('UserCard', () => {
  it('renders user name', () => {
    render(<UserCard user={{ id: 1, name: 'Alice', email: 'a@b.com' }} />);
    expect(screen.getByText('Alice')).toBeInTheDocument();
    expect(screen.getByRole('heading', { name: 'Alice' })).toBeInTheDocument();
  });

  it('calls onDelete when button clicked', async () => {
    const onDelete = jest.fn();
    const user = userEvent.setup();
    render(<UserCard user={{ id: 1, name: 'Alice' }} onDelete={onDelete} />);

    await user.click(screen.getByRole('button', { name: /delete/i }));
    expect(onDelete).toHaveBeenCalledWith(1);
  });

  it('shows loading state', async () => {
    render(<UserCard userId={1} />);
    expect(screen.getByRole('progressbar')).toBeInTheDocument();
    await waitFor(() => expect(screen.getByText('Alice')).toBeInTheDocument());
  });

  // Prefer queries: getBy (throws), queryBy (null), findBy (async)
  // getByRole > getByLabelText > getByText > getByTestId (last resort)
});

Snapshot Testing

// Snapshot — save rendered output, fail when it changes
it('matches snapshot', () => {
  const { container } = render(<Button label="Click me" />);
  expect(container).toMatchSnapshot();
});

// Inline snapshot
expect(user).toMatchInlineSnapshot(`
  Object {
    "email": "alice@example.com",
    "id": 1,
    "name": "Alice",
  }
`);

// Update snapshots: jest --updateSnapshot (or jest -u)
// Use sparingly — snapshots of large components are brittle

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free