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