Jest Mock 功能 #

Mock 概述 #

Mock 是测试中模拟依赖和行为的技术,用于隔离测试单元、控制外部依赖、验证函数调用。

text
┌─────────────────────────────────────────────────────────────┐
│                    Mock 的作用                               │
├─────────────────────────────────────────────────────────────┤
│  1. 隔离测试单元 - 不依赖外部系统                            │
│  2. 控制测试输入 - 模拟各种场景                              │
│  3. 验证函数调用 - 检查调用次数和参数                        │
│  4. 提高测试速度 - 避免真实操作                              │
└─────────────────────────────────────────────────────────────┘

函数 Mock #

jest.fn() #

创建一个 Mock 函数:

javascript
test('jest.fn() basic', () => {
  const mockFn = jest.fn();

  mockFn('hello');
  mockFn('world');

  expect(mockFn).toHaveBeenCalled();
  expect(mockFn).toHaveBeenCalledTimes(2);
  expect(mockFn).toHaveBeenCalledWith('hello');
  expect(mockFn).toHaveBeenLastCalledWith('world');
});

返回值 #

javascript
test('mock return values', () => {
  const mockFn = jest.fn();

  // 固定返回值
  mockFn.mockReturnValue(42);
  expect(mockFn()).toBe(42);

  // 一次性返回值
  mockFn.mockReturnValueOnce(1);
  mockFn.mockReturnValueOnce(2);
  expect(mockFn()).toBe(1);
  expect(mockFn()).toBe(2);
  expect(mockFn()).toBe(42); // 回到默认值
});

实现 #

javascript
test('mock implementation', () => {
  const mockFn = jest.fn();

  // 自定义实现
  mockFn.mockImplementation((a, b) => a + b);
  expect(mockFn(1, 2)).toBe(3);

  // 一次性实现
  mockFn.mockImplementationOnce(() => 'first');
  mockFn.mockImplementationOnce(() => 'second');
  expect(mockFn()).toBe('first');
  expect(mockFn()).toBe('second');
  expect(mockFn()).toBe(3); // 回到默认实现
});

返回 Promise #

javascript
test('mock promise', async () => {
  const mockFn = jest.fn();

  // resolve
  mockFn.mockResolvedValue('success');
  await expect(mockFn()).resolves.toBe('success');

  // reject
  mockFn.mockRejectedValue(new Error('failed'));
  await expect(mockFn()).rejects.toThrow('failed');

  // 一次性 resolve
  mockFn.mockResolvedValueOnce('first');
  mockFn.mockResolvedValueOnce('second');
  await expect(mockFn()).resolves.toBe('first');
  await expect(mockFn()).resolves.toBe('second');
});

调用信息 #

javascript
test('mock call info', () => {
  const mockFn = jest.fn((x) => x * 2);

  mockFn(1);
  mockFn(2);
  mockFn(3);

  // 调用次数
  expect(mockFn.mock.calls.length).toBe(3);

  // 每次调用的参数
  expect(mockFn.mock.calls[0]).toEqual([1]);
  expect(mockFn.mock.calls[1]).toEqual([2]);
  expect(mockFn.mock.calls[2]).toEqual([3]);

  // 每次调用的返回值
  expect(mockFn.mock.results[0].value).toBe(2);
  expect(mockFn.mock.results[1].value).toBe(4);
  expect(mockFn.mock.results[2].value).toBe(6);
});

模块 Mock #

jest.mock() #

自动 Mock 整个模块:

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

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

// math.test.js
import * as math from './math';

jest.mock('./math');

test('mocked module', () => {
  math.add(1, 2);
  expect(math.add).toHaveBeenCalled();
  expect(math.add).toHaveBeenCalledWith(1, 2);
});

手动 Mock 实现 #

javascript
import * as math from './math';

jest.mock('./math', () => ({
  add: jest.fn((a, b) => a + b),
  subtract: jest.fn((a, b) => a - b),
}));

test('custom mock implementation', () => {
  expect(math.add(1, 2)).toBe(3);
  expect(math.subtract(5, 3)).toBe(2);
});

部分 Mock #

javascript
import * as math from './math';

jest.mock('./math', () => {
  const original = jest.requireActual('./math');
  return {
    ...original,
    add: jest.fn(() => 100), // 只 Mock add
  };
});

test('partial mock', () => {
  expect(math.add(1, 2)).toBe(100); // Mocked
  expect(math.subtract(5, 3)).toBe(2); // Original
});

Mock Node 模块 #

javascript
// Mock fs
jest.mock('fs', () => ({
  readFileSync: jest.fn(() => 'mocked content'),
  writeFileSync: jest.fn(),
}));

const fs = require('fs');

test('mock fs', () => {
  const content = fs.readFileSync('test.txt', 'utf-8');
  expect(content).toBe('mocked content');
});

Mock axios #

javascript
import axios from 'axios';

jest.mock('axios');

test('mock axios', async () => {
  const users = [{ id: 1, name: 'John' }];
  axios.get.mockResolvedValue({ data: users });

  const result = await axios.get('/api/users');
  expect(result.data).toEqual(users);
});

Spy 监视 #

jest.spyOn() #

监视对象方法,保留原实现:

javascript
const video = {
  play() {
    return true;
  },
  pause() {
    return true;
  },
};

test('spy on method', () => {
  const spy = jest.spyOn(video, 'play');

  video.play();

  expect(spy).toHaveBeenCalled();
  expect(spy).toHaveReturnedWith(true);
});

Mock 实现 #

javascript
test('spy with mock implementation', () => {
  const spy = jest.spyOn(video, 'play');
  spy.mockReturnValue(false);

  expect(video.play()).toBe(false);

  spy.mockRestore(); // 恢复原实现
});

监视 getter/setter #

javascript
const obj = {
  get name() {
    return 'John';
  },
  set name(value) {
    this._name = value;
  },
};

test('spy on getter/setter', () => {
  const getterSpy = jest.spyOn(obj, 'name', 'get');
  const setterSpy = jest.spyOn(obj, 'name', 'set');

  expect(obj.name).toBe('John');
  obj.name = 'Jane';

  expect(getterSpy).toHaveBeenCalled();
  expect(setterSpy).toHaveBeenCalledWith('Jane');
});

定时器 Mock #

jest.useFakeTimers() #

javascript
describe('timer mock', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  test('setTimeout', () => {
    const callback = jest.fn();
    setTimeout(callback, 1000);

    jest.advanceTimersByTime(1000);
    expect(callback).toHaveBeenCalled();
  });

  test('setInterval', () => {
    const callback = jest.fn();
    setInterval(callback, 1000);

    jest.advanceTimersByTime(5000);
    expect(callback).toHaveBeenCalledTimes(5);
  });
});

控制时间 #

javascript
test('control time', () => {
  jest.useFakeTimers();

  const callback = jest.fn();
  setTimeout(callback, 1000);

  // 快进 500ms
  jest.advanceTimersByTime(500);
  expect(callback).not.toHaveBeenCalled();

  // 快进剩余 500ms
  jest.advanceTimersByTime(500);
  expect(callback).toHaveBeenCalled();

  jest.useRealTimers();
});

运行所有定时器 #

javascript
test('run all timers', () => {
  jest.useFakeTimers();

  const callback1 = jest.fn();
  const callback2 = jest.fn();

  setTimeout(callback1, 1000);
  setTimeout(callback2, 2000);

  jest.runAllTimers();

  expect(callback1).toHaveBeenCalled();
  expect(callback2).toHaveBeenCalled();

  jest.useRealTimers();
});

Mock 清理 #

jest.clearAllMocks() #

清除所有 Mock 的调用记录:

javascript
beforeEach(() => {
  jest.clearAllMocks();
});

jest.resetAllMocks() #

清除所有 Mock 的调用记录和实现:

javascript
beforeEach(() => {
  jest.resetAllMocks();
});

jest.restoreAllMocks() #

恢复所有被 spyOn 的方法:

javascript
afterEach(() => {
  jest.restoreAllMocks();
});

mocks 目录 #

手动 Mock 文件 #

text
__mocks__/
├── fs.js
├── axios.js
└── lodash.js

src/
├── utils.js
└── utils.test.js
javascript
// __mocks__/axios.js
const axios = {
  get: jest.fn(() => Promise.resolve({ data: {} })),
  post: jest.fn(() => Promise.resolve({ data: {} })),
};

export default axios;

// src/utils.test.js
import axios from 'axios';

jest.mock('axios');

test('uses manual mock', async () => {
  const data = await axios.get('/api/users');
  expect(data).toEqual({ data: {} });
});

实际应用示例 #

Mock API 请求 #

javascript
// api.js
import axios from 'axios';

export async function fetchUser(id) {
  const response = await axios.get(`/api/users/${id}`);
  return response.data;
}

// api.test.js
import axios from 'axios';
import { fetchUser } from './api';

jest.mock('axios');

describe('fetchUser', () => {
  test('fetches user successfully', async () => {
    const user = { id: 1, name: 'John' };
    axios.get.mockResolvedValue({ data: user });

    const result = await fetchUser(1);

    expect(axios.get).toHaveBeenCalledWith('/api/users/1');
    expect(result).toEqual(user);
  });

  test('handles error', async () => {
    axios.get.mockRejectedValue(new Error('Network error'));

    await expect(fetchUser(1)).rejects.toThrow('Network error');
  });
});

Mock 数据库 #

javascript
// database.js
class Database {
  async connect() {
    // 真实连接逻辑
  }

  async query(sql) {
    // 真实查询逻辑
  }
}

export const db = new Database();

// database.test.js
import { db } from './database';

jest.mock('./database', () => ({
  db: {
    connect: jest.fn(),
    query: jest.fn(),
  },
}));

test('database operations', async () => {
  db.query.mockResolvedValue([{ id: 1, name: 'John' }]);

  const result = await db.query('SELECT * FROM users');

  expect(db.query).toHaveBeenCalledWith('SELECT * FROM users');
  expect(result).toEqual([{ id: 1, name: 'John' }]);
});

Mock localStorage #

javascript
const localStorageMock = {
  getItem: jest.fn(),
  setItem: jest.fn(),
  removeItem: jest.fn(),
  clear: jest.fn(),
};

global.localStorage = localStorageMock;

test('localStorage mock', () => {
  localStorageMock.getItem.mockReturnValue('test value');

  const value = localStorage.getItem('key');

  expect(localStorageMock.getItem).toHaveBeenCalledWith('key');
  expect(value).toBe('test value');
});

Mock fetch #

javascript
global.fetch = jest.fn();

test('mock fetch', async () => {
  const users = [{ id: 1, name: 'John' }];
  fetch.mockResolvedValue({
    ok: true,
    json: () => Promise.resolve(users),
  });

  const response = await fetch('/api/users');
  const data = await response.json();

  expect(fetch).toHaveBeenCalledWith('/api/users');
  expect(data).toEqual(users);
});

Mock console #

javascript
test('mock console', () => {
  const logSpy = jest.spyOn(console, 'log').mockImplementation();

  console.log('test message');

  expect(logSpy).toHaveBeenCalledWith('test message');

  logSpy.mockRestore();
});

Mock Date #

javascript
test('mock date', () => {
  jest.useFakeTimers();
  jest.setSystemTime(new Date('2024-01-01'));

  expect(new Date().getFullYear()).toBe(2024);

  jest.useRealTimers();
});

Mock 最佳实践 #

1. 保持 Mock 简单 #

javascript
// ❌ 复杂的 Mock
jest.mock('./api', () => {
  const original = jest.requireActual('./api');
  return {
    ...original,
    fetch: jest.fn(() => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve({ data: 'test' });
        }, 100);
      });
    }),
  };
});

// ✅ 简单的 Mock
jest.mock('./api', () => ({
  fetch: jest.fn().mockResolvedValue({ data: 'test' }),
}));

2. 清理 Mock #

javascript
afterEach(() => {
  jest.clearAllMocks();
});

3. 使用 spyOn 保留原实现 #

javascript
// 当需要监视但保留原实现时
test('spy on method', () => {
  const spy = jest.spyOn(obj, 'method');

  obj.method();

  expect(spy).toHaveBeenCalled();

  spy.mockRestore();
});

4. Mock 边界 #

javascript
// 只 Mock 外部依赖,不 Mock 内部逻辑
// ❌ 不要 Mock 被测试的模块
jest.mock('./calculator'); // 错误

// ✅ Mock 外部依赖
jest.mock('./database');
jest.mock('./api');

常见问题 #

Mock 不生效 #

javascript
// 问题:Mock 在导入之后
import { fetchData } from './api';
jest.mock('./api'); // 太晚了

// 解决方案:Mock 必须在导入之前
jest.mock('./api');
import { fetchData } from './api';

无法 Mock 默认导出 #

javascript
// 问题
import axios from 'axios';
jest.mock('axios'); // 可能不工作

// 解决方案
jest.mock('axios', () => ({
  default: {
    get: jest.fn(),
  },
}));

Mock 污染其他测试 #

javascript
// 问题:Mock 影响其他测试
test('test 1', () => {
  jest.spyOn(obj, 'method').mockReturnValue(1);
});

test('test 2', () => {
  // obj.method 仍然是 Mock
});

// 解决方案:清理 Mock
afterEach(() => {
  jest.restoreAllMocks();
});

下一步 #

现在你已经掌握了 Jest Mock 功能,接下来学习 快照测试 学习 UI 组件测试!

最后更新:2026-03-28