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