Jest 异步测试 #
异步测试概述 #
JavaScript 中异步代码非常常见,Jest 提供了多种方式来测试异步代码:
text
┌─────────────────────────────────────────────────────────────┐
│ 异步测试方式 │
├─────────────────────────────────────────────────────────────┤
│ 1. 回调函数 (done) │
│ 2. Promise (return) │
│ 3. async/await │
│ 4. resolves/rejects │
│ 5. 定时器 Mock │
└─────────────────────────────────────────────────────────────┘
回调函数测试 #
done 参数 #
当测试函数接受 done 参数时,Jest 会等待 done() 被调用:
javascript
function fetchData(callback) {
setTimeout(() => {
callback('peanut butter');
}, 100);
}
test('callback test', done => {
function callback(data) {
expect(data).toBe('peanut butter');
done();
}
fetchData(callback);
});
错误处理 #
javascript
test('callback with error', done => {
function callback(error, data) {
if (error) {
done(error);
return;
}
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
超时设置 #
javascript
test('callback with timeout', done => {
setTimeout(() => {
expect(true).toBe(true);
done();
}, 1000);
}, 5000); // 设置 5 秒超时
Promise 测试 #
返回 Promise #
直接返回 Promise,Jest 会自动等待:
javascript
function fetchData() {
return new Promise(resolve => {
setTimeout(() => resolve('peanut butter'), 100);
});
}
test('promise test', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
错误情况 #
javascript
function fetchError() {
return Promise.reject('error');
}
test('promise rejection', () => {
return fetchError().catch(error => {
expect(error).toBe('error');
});
});
使用 .resolves / .rejects #
javascript
test('resolves', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});
test('rejects', () => {
return expect(fetchError()).rejects.toMatch('error');
});
test('rejects with error type', () => {
return expect(fetchError()).rejects.toThrow('error');
});
async/await 测试 #
基本用法 #
javascript
function fetchData() {
return new Promise(resolve => {
setTimeout(() => resolve('peanut butter'), 100);
});
}
test('async test', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
结合 resolves/rejects #
javascript
test('async with resolves', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('async with rejects', async () => {
await expect(fetchError()).rejects.toThrow('error');
});
错误处理 #
javascript
test('async error handling', async () => {
try {
await fetchError();
} catch (error) {
expect(error).toBe('error');
}
});
test('async with try/catch', async () => {
await expect(fetchError()).rejects.toThrow();
});
并行执行 #
javascript
test('parallel async tests', async () => {
const [users, posts] = await Promise.all([
fetchUsers(),
fetchPosts()
]);
expect(users).toHaveLength(10);
expect(posts).toHaveLength(5);
});
定时器测试 #
假定时器 #
javascript
describe('timer tests', () => {
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);
// 快进 3 秒
jest.advanceTimersByTime(3000);
expect(callback).toHaveBeenCalledTimes(3);
});
});
快进所有定时器 #
javascript
test('run all timers', () => {
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 1000);
jest.runAllTimers();
expect(callback).toHaveBeenCalled();
jest.useRealTimers();
});
快进到下一个定时器 #
javascript
test('run only pending timers', () => {
jest.useFakeTimers();
const callback1 = jest.fn();
const callback2 = jest.fn();
setTimeout(callback1, 1000);
setTimeout(callback2, 2000);
jest.runOnlyPendingTimers();
expect(callback1).toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
jest.useRealTimers();
});
快进指定时间 #
javascript
test('advance timers by time', () => {
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(500);
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(500);
expect(callback).toHaveBeenCalled();
jest.useRealTimers();
});
模拟日期 #
javascript
test('mock date', () => {
jest.useFakeTimers();
const now = new Date('2024-01-01');
jest.setSystemTime(now);
expect(new Date()).toEqual(now);
jest.useRealTimers();
});
实际应用示例 #
API 请求测试 #
javascript
describe('API tests', () => {
test('fetches user data', async () => {
const user = await fetchUser(1);
expect(user).toHaveProperty('id', 1);
expect(user).toHaveProperty('name');
});
test('handles network error', async () => {
jest.spyOn(global, 'fetch').mockRejectedValue(new Error('Network error'));
await expect(fetchUser(1)).rejects.toThrow('Network error');
});
test('handles 404 error', async () => {
jest.spyOn(global, 'fetch').mockResolvedValue({
ok: false,
status: 404,
});
await expect(fetchUser(999)).rejects.toThrow('Not found');
});
});
数据库操作测试 #
javascript
describe('Database operations', () => {
let db;
beforeAll(async () => {
db = await connectDatabase();
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
await db.clear();
});
test('inserts user', async () => {
const user = await db.insert({ name: 'John' });
expect(user.id).toBeDefined();
expect(user.name).toBe('John');
});
test('finds user by id', async () => {
const created = await db.insert({ name: 'John' });
const found = await db.findById(created.id);
expect(found).toEqual(created);
});
test('updates user', async () => {
const user = await db.insert({ name: 'John' });
const updated = await db.update(user.id, { name: 'Jane' });
expect(updated.name).toBe('Jane');
});
});
文件操作测试 #
javascript
const fs = require('fs').promises;
describe('File operations', () => {
const testFile = './test-file.txt';
afterEach(async () => {
try {
await fs.unlink(testFile);
} catch (error) {
// 文件不存在,忽略
}
});
test('writes and reads file', async () => {
await fs.writeFile(testFile, 'Hello, World!');
const content = await fs.readFile(testFile, 'utf-8');
expect(content).toBe('Hello, World!');
});
test('handles non-existent file', async () => {
await expect(fs.readFile('./non-existent.txt', 'utf-8'))
.rejects.toThrow();
});
});
延迟函数测试 #
javascript
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
describe('delay and debounce', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
test('delay function', async () => {
const start = Date.now();
const promise = delay(1000);
jest.advanceTimersByTime(1000);
await promise;
expect(Date.now() - start).toBe(1000);
});
test('debounce function', () => {
const fn = jest.fn();
const debounced = debounce(fn, 100);
debounced();
debounced();
debounced();
expect(fn).not.toHaveBeenCalled();
jest.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledTimes(1);
});
});
事件监听测试 #
javascript
const EventEmitter = require('events');
describe('EventEmitter', () => {
let emitter;
beforeEach(() => {
emitter = new EventEmitter();
});
test('emits event', done => {
emitter.on('message', (data) => {
expect(data).toBe('hello');
done();
});
emitter.emit('message', 'hello');
});
test('emits multiple events', done => {
const messages = [];
emitter.on('message', (data) => {
messages.push(data);
if (messages.length === 3) {
expect(messages).toEqual(['a', 'b', 'c']);
done();
}
});
emitter.emit('message', 'a');
emitter.emit('message', 'b');
emitter.emit('message', 'c');
});
});
异步测试最佳实践 #
1. 始终返回 Promise #
javascript
// ❌ 不好的做法
test('bad', () => {
fetchData().then(data => {
expect(data).toBe('peanut butter');
});
// 测试可能在 Promise 完成前结束
});
// ✅ 好的做法
test('good', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
2. 使用 async/await #
javascript
// ❌ 不好的做法
test('bad', () => {
return fetchData().then(data => {
return fetchMore(data).then(result => {
expect(result).toBe('expected');
});
});
});
// ✅ 好的做法
test('good', async () => {
const data = await fetchData();
const result = await fetchMore(data);
expect(result).toBe('expected');
});
3. 正确处理错误 #
javascript
// ❌ 不好的做法
test('bad', async () => {
try {
await fetchError();
} catch (error) {
expect(error).toBeDefined();
}
});
// ✅ 好的做法
test('good', async () => {
await expect(fetchError()).rejects.toThrow();
});
4. 设置合理的超时时间 #
javascript
// 对于慢测试,设置更长的超时
test('slow test', async () => {
const data = await slowOperation();
expect(data).toBeDefined();
}, 10000); // 10 秒超时
5. 清理定时器 #
javascript
describe('timer tests', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
jest.clearAllTimers();
});
test('timer test', () => {
// 测试代码
});
});
常见问题 #
测试提前结束 #
javascript
// 问题:测试在异步操作完成前结束
test('problem', () => {
fetchData().then(data => {
expect(data).toBe('expected');
});
// 没有 return 或 await
});
// 解决方案
test('solution', async () => {
const data = await fetchData();
expect(data).toBe('expected');
});
定时器不执行 #
javascript
// 问题:定时器不会真正执行
test('problem', () => {
setTimeout(() => {
expect(true).toBe(true);
}, 1000);
// 测试立即结束
});
// 解决方案
test('solution', () => {
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
jest.useRealTimers();
});
并发问题 #
javascript
// 问题:多个异步操作竞争
test('problem', async () => {
let value = 0;
setTimeout(() => value++, 100);
setTimeout(() => value++, 200);
await new Promise(resolve => setTimeout(resolve, 300));
expect(value).toBe(2);
});
// 解决方案:使用假定时器
test('solution', () => {
jest.useFakeTimers();
let value = 0;
setTimeout(() => value++, 100);
setTimeout(() => value++, 200);
jest.advanceTimersByTime(300);
expect(value).toBe(2);
jest.useRealTimers();
});
下一步 #
现在你已经掌握了 Jest 异步测试,接下来学习 Mock 功能 学习如何模拟依赖!
最后更新:2026-03-28