使用Jest测试嵌套Promise的createCampaign接口失败求助
问题描述
我正在测试的createCampaign函数采用嵌套Promise结构,目前无法改为async/await写法。该函数代码如下:
exports.createCampaign = (req, res) => { const { name } = req.body; User.findByPk(req.userId).then((user) => { Account.findOne({ where: { id: user.accountId } }).then((account) => { Campaign.create({ accountId: account.id, name: name, uuid: uuid.v4(), }) .then((campaign) => { if (campaign) { return res.status(201).json({ id: campaign.id, name: campaign.name, uuid: campaign.uuid, }); } return res.status(500).send({ message: 'Campaign not created' }); }) .catch((err) => { return res.status(500).send({ message: err.message }); }); }); }); };
我编写的Jest测试用例如下:
jest.mock('@545490/kgr-models', () => ({ user: { findByPk: jest.fn(), }, account: { findOne: jest.fn(), }, campaign: { create: jest.fn(), findOne: jest.fn(), }, })); describe('CampaignController', () => { let mockStatus, mockJson, mockRes, mockSend; mockJson = jest.fn(); mockSend = jest.fn(); mockStatus = jest.fn(() => ({ json: mockJson, send: mockSend })); mockRes = { status: mockStatus }; const mockUser = { id: 'userId', firstName: 'firstName', lastName: 'lastName', email: 'email', accountId: 'accountId', }; const mockAccount = { id: 'accountId', name: 'accountName', partner: 'partner', }; const mockCampaign = { id: 'campaignId', name: 'campaignName', accountId: 'accountId', status: 'active', }; afterEach(() => { jest.clearAllMocks(); }); describe('createCampaign', () => { it('should create a campaign', async () => { User.findByPk.mockResolvedValue(mockUser); Account.findOne.mockResolvedValue(mockAccount); Campaign.create.mockResolvedValue(mockCampaign); await createCampaign({ body: { name: 'campaignName', }, userId: 'userId', }, mockRes); expect(User.findByPk).toHaveBeenCalledWith('userId'); expect(Account.findOne).toHaveBeenCalledWith({ where: { id: mockUser.accountId } }); expect(Campaign.create).toHaveBeenCalled(); expect(mockStatus).toHaveBeenCalledWith(201); expect(mockJson).toHaveBeenCalled(); }); }); });
执行测试后出现失败,我怀疑是嵌套Promise的Mock方式不正确。尝试将原函数改为async/await结构后测试可正常通过,但目前不允许修改原函数,现求助如何正确测试该嵌套Promise结构的函数。
解决方案
问题核心是原函数没有返回完整的Promise链,导致Jest的await无法等待所有嵌套异步操作完成,测试会在内部Promise执行前就结束,断言自然失效。以下是两种可行的解决方式:
方式一:最小化修改原函数(仅添加return)
如果允许对原函数做极少量修改,只需要给每个嵌套的Promise链添加return,让整个异步流程的Promise被返回,这样测试中的await就能正确等待所有步骤完成:
exports.createCampaign = (req, res) => { const { name } = req.body; // 返回最外层Promise return User.findByPk(req.userId).then((user) => { // 返回Account查询的Promise return Account.findOne({ where: { id: user.accountId } }).then((account) => { // 返回Campaign创建的Promise return Campaign.create({ accountId: account.id, name: name, uuid: uuid.v4(), }) .then((campaign) => { if (campaign) { return res.status(201).json({ id: campaign.id, name: campaign.name, uuid: campaign.uuid, }); } return res.status(500).send({ message: 'Campaign not created' }); }) .catch((err) => { return res.status(500).send({ message: err.message }); }); }); }); };
修改后原测试用例无需调整即可正常通过。
方式二:完全不修改原函数,调整测试用例
如果完全不能改动原函数,需要在测试中手动等待所有Mock的Promise执行完成后再断言,有两种可靠实现方式:
方法A:等待所有Mock的Promise resolve
it('should create a campaign', async () => { User.findByPk.mockResolvedValue(mockUser); Account.findOne.mockResolvedValue(mockAccount); Campaign.create.mockResolvedValue(mockCampaign); // 执行目标函数 createCampaign({ body: { name: 'campaignName' }, userId: 'userId', }, mockRes); // 等待所有Mock的Promise完成 await Promise.all([ User.findByPk.mock.results[0].value, Account.findOne.mock.results[0].value, Campaign.create.mock.results[0].value ]); // 执行断言 expect(User.findByPk).toHaveBeenCalledWith('userId'); expect(Account.findOne).toHaveBeenCalledWith({ where: { id: mockUser.accountId } }); expect(Campaign.create).toHaveBeenCalledWith({ accountId: mockAccount.id, name: 'campaignName', uuid: expect.any(String) // 验证uuid格式 }); expect(mockStatus).toHaveBeenCalledWith(201); expect(mockJson).toHaveBeenCalledWith({ id: mockCampaign.id, name: mockCampaign.name, uuid: expect.any(String) }); });
方法B:使用done回调配合setTimeout
it('should create a campaign', (done) => { User.findByPk.mockResolvedValue(mockUser); Account.findOne.mockResolvedValue(mockAccount); Campaign.create.mockResolvedValue(mockCampaign); // 执行目标函数 createCampaign({ body: { name: 'campaignName' }, userId: 'userId', }, mockRes); // 等待事件循环清空,确保所有嵌套Promise执行完成 setTimeout(() => { expect(User.findByPk).toHaveBeenCalledWith('userId'); expect(Account.findOne).toHaveBeenCalledWith({ where: { id: mockUser.accountId } }); expect(Campaign.create).toHaveBeenCalled(); expect(mockStatus).toHaveBeenCalledWith(201); expect(mockJson).toHaveBeenCalled(); done(); // 通知Jest测试完成 }, 0); });
关键原因说明
原函数的嵌套Promise没有被返回,导致createCampaign执行后立即返回undefined,测试里的await相当于等待一个非Promise值,不会等待内部异步操作结束。此时断言执行时,嵌套的Promise可能还没走到Campaign.create或res.status的步骤,导致断言失败。
内容的提问来源于stack exchange,提问作者Ryan Garde
相关产品推荐
相关产品推荐

