如何在不暴露内部函数及不用超时的情况下测试JS异步函数结果?
异步邮件发送测试问题解决方案
问题背景
现有一段模拟账户创建的代码,createAccount函数会将账户信息存入数组,随后异步调用sendEmail模拟发送通知邮件,但不会等待邮件发送完成。编写测试用例验证邮件发送时,因sendEmail的异步特性,断言执行时邮件还未发送完成,导致测试失败。
原账户创建代码
export const accounts = []; export const emails = []; export async function createAccount(name, email) { accounts.push({name, email}); void sendEmail(email); } async function sendEmail(email) { setTimeout(0, () => emails.push(`Account created to ${email}`)); }
原测试代码
import {assert} from 'chai'; import {createAccount, emails} from './index.js'; describe('test', async () => { it('test', async() => { await createAccount('John Doe', 'test@example.com'); assert(emails[0], 'Account created to test@example.com'); }); });
测试报错信息
$ npm run test > foo@1.0.0 test > mocha test.js test 1) test 0 passing (5ms) 1 failing 1) test test: AssertionError: Account created to test@example.com at Context.<anonymous> (file:///home/me/sandbox/foo/test.js:7:5)
问题原因
sendEmail内部使用setTimeout触发异步操作,属于宏任务。测试中await createAccount仅等待createAccount函数本身完成,不会等待sendEmail里的setTimeout回调执行,因此断言时emails数组还未被填充。
要求
- 不暴露
sendEmail的返回值; - 不使用睡眠/超时机制。
解决方案
方案1:Mock全局setTimeout同步执行回调
通过替换全局的setTimeout,让其立即执行传入的回调函数,消除异步延迟,确保测试时emails数组能被同步填充。
修改后的测试代码:
import { assert } from 'chai'; import { createAccount, emails } from './index.js'; describe('账户创建邮件测试', () => { let originalSetTimeout; beforeEach(() => { // 保存原始setTimeout originalSetTimeout = global.setTimeout; // Mock setTimeout为同步执行 global.setTimeout = (callback) => { callback(); return 0; }; // 清空emails数组,避免测试污染 emails.length = 0; }); afterEach(() => { // 恢复原始setTimeout global.setTimeout = originalSetTimeout; }); it('应成功发送账户创建通知邮件', async () => { await createAccount('John Doe', 'test@example.com'); assert.equal(emails[0], 'Account created to test@example.com'); }); });
方案2:利用Node.js事件循环机制等待宏任务执行
通过setImmediate封装Promise,等待当前事件循环中的所有宏任务(包括setTimeout的回调)执行完毕后再进行断言。
修改后的测试代码:
import { assert } from 'chai'; import { createAccount, emails } from './index.js'; describe('账户创建邮件测试', () => { beforeEach(() => { emails.length = 0; }); it('应成功发送账户创建通知邮件', async () => { await createAccount('John Doe', 'test@example.com'); // 等待当前宏任务队列执行完成 await new Promise(resolve => setImmediate(resolve)); assert.equal(emails[0], 'Account created to test@example.com'); }); });
附录:测试环境配置(package.json)
{ "name": "foo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "mocha test.js" }, "type": "module", "author": "", "license": "ISC", "devDependencies": { "chai": "^5.1.1", "mocha": "^10.6.0" } }
内容的提问来源于stack exchange,提问作者brandizzi
相关产品推荐
相关产品推荐

