如何测试Promise.all因map未返回Promise导致的异步执行问题?
如何通过测试避免异步map回调未返回Promise的bug
这是个特别容易踩的异步代码坑!除了全量集成测试,你可以靠下面几种单元测试+静态检查的组合策略,提前把这类bug扼杀在摇篮里:
1. 测试Promise.all接收的数组是否全为Promise实例
核心思路是:mock掉返回Promise的方法,让它返回一个带标记的Promise实例,然后捕获传给Promise.all的参数,检查每一项都是合法的Promise,而非undefined。
示例测试代码(用Jest为例):
test('myMethod passes valid Promises to Promise.all', async () => { // 自定义一个用于标记的Promise子类 class TrackedPromise extends Promise {} // mock目标异步方法,返回我们的标记Promise jest.mock('./your-module-path', () => ({ someMethodThatReturnsPromise: jest.fn(id => new TrackedPromise(resolve => resolve(id))) })); // 保存原始的Promise.all,测试后恢复 const originalPromiseAll = Promise.all; let receivedPromises = []; // 替换Promise.all,捕获传入的参数 Promise.all = jest.fn(promises => { receivedPromises = promises; return originalPromiseAll(promises); }); await myMethod(); // 断言每一项都是我们标记的Promise实例 receivedPromises.forEach(promise => { expect(promise).toBeInstanceOf(TrackedPromise); }); // 恢复原始方法,避免影响其他测试 Promise.all = originalPromiseAll; });
如果map回调忘记返回Promise,receivedPromises里就会混入undefined,这个断言会直接失败,帮你快速定位问题。
2. 验证后续逻辑严格在所有异步操作完成后执行
通过手动控制异步方法的resolve时机,测试后续代码的执行顺序是否符合预期:
示例测试代码:
test('doSomethingElse runs only after all async operations finish', async () => { // 用来手动控制resolve的延迟对象数组 const deferreds = []; // mock异步方法,返回可手动resolve的Promise jest.mock('./your-module-path', () => ({ someMethodThatReturnsPromise: jest.fn(id => { const deferred = {}; deferred.promise = new Promise(resolve => { deferred.resolve = resolve; }); deferreds.push(deferred); return deferred.promise; }) })); // mock后续执行的方法,方便检查调用时机 const mockDoSomething = jest.spyOn(global, 'doSomethingElseAfterPromisesResolve') .mockImplementation(() => {}); // 调用myMethod但不等待完成 const myMethodExecution = myMethod(); // 此时所有异步还没resolve,断言后续方法未被调用 expect(mockDoSomething).not.toHaveBeenCalled(); // 手动resolve所有异步操作 deferreds.forEach(d => d.resolve()); // 等待myMethod执行完成 await myMethodExecution; // 此时断言后续方法已被调用 expect(mockDoSomething).toHaveBeenCalled(); });
如果Promise.all因为接收了undefined而没有等待,doSomethingElseAfterPromisesResolve会在调用myMethod后立刻执行,第一个断言就会失败,直接暴露问题。
3. 用静态代码分析提前预警(配合测试)
虽然这不是单元测试,但能在编码阶段就帮你避免这类错误,和测试相辅相成:
- ESLint:开启
array-callback-return规则,当你在map这类数组方法的回调里用了大括号却没写return时,会直接给出语法警告。 - TypeScript:如果
someMethodThatReturnsPromise的返回类型是Promise<T>,那么map后的数组类型应该是Promise<T>[];如果没return,数组类型会变成undefined[],传给Promise.all时TypeScript会抛出类型错误,因为Promise.all只接受Iterable<PromiseLike<any>>类型的参数。
内容的提问来源于stack exchange,提问作者Damon Black
相关产品推荐
相关产品推荐

