Jest测试超时:article.controller.js的editArticle测试失败排查
Jest单元测试超时排查:editArticle测试用例超时问题
问题描述
在排查article.controller.js的单元测试时,Jest抛出超时错误:Exceeded timeout of 5000 ms for a test,错误定位到editArticle tests下的If user can edit an article测试用例,无法确定具体原因。
控制器代码(article.controller.js)
const { saveArticle, updateArticle, removeArticle, getArticle, getAllTags, getAllArticles, feedArticles, favoriteArticle, unFavoriteArticle, } = require('../services/article.service'); exports.editArticle = async (req, res, next) => { const slugText = req.params.slug; if (slugText) { return updateArticle(slugText, req.user._id, req.body.article) .then((isUpdated) => { if (isUpdated) { return res.status(200).json({ article: isUpdated }); } }) .catch(next); } };
单元测试代码
const articleServiceHelper = require("../../../services/article.service"); const mockResponse = () => { return { status: jest.fn().mockReturnThis(), json: jest.fn().mockReturnThis() }; }; const mockNext = jest.fn(); const mockUser = { _id: "6368ad4347dkhs" }; const mockUpdatedArticle = { title: "Updated article title", description: "Updated description", body: "Updated body" } describe("Unit tests for article.controller.js file", () => { describe("editArticle tests", () => { test("If user can edit an article", async () => { //Arrange const mockReq = { params: { slug: 'test-article-slug' }, user: { _id: "6368ad4347dkhs" }, body: { article: mockUpdatedArticle } }; const mockRes = mockResponse(); const updateArticleSpy = jest.spyOn(articleServiceHelper, "updateArticle").mockResolvedValue(mockUpdatedArticle); const { editArticle } = require("../../../controllers/article.controller"); //Act const updatedArticle = await editArticle(mockReq, mockRes, mockNext); //Assert expect(updatedArticle).toBe(mockUpdatedArticle); expect(updateArticleSpy).toHaveBeenCalledTimes(1); expect(updateArticleSpy).toHaveBeenCalledWith(mockReq.params.slug, mockUser._id, mockReq.body.article); expect(mockRes.status).toHaveBeenCalledWith(200); expect(mockRes.json).toHaveBeenCalledWith({ article: mockUpdatedArticle }); expect(mockNext).not.toHaveBeenCalled(); }); }); });
排查分析
超时的核心原因是控制器的editArticle函数存在未处理的Promise状态分支:
- 当
updateArticle返回isUpdated时,控制器返回的是res.status(200).json(...)的结果——而res.json被mock成返回自身的链式调用对象,并非Promise,导致await editArticle(...)无法正确解析Promise状态,测试会一直等待直到超时。 - 测试中错误断言
expect(updatedArticle).toBe(mockUpdatedArticle):editArticle的返回值是res.json()的mock对象,而非mockUpdatedArticle,这一错误逻辑也间接导致测试流程异常。 - 测试用例内部执行
require("../../../controllers/article.controller"),可能引发模块缓存问题,干扰测试执行逻辑。
修复方案
1. 修正控制器代码(统一async/await语法)
将控制器改为纯async/await写法,确保所有分支都返回明确的Promise状态,避免混用.then():
exports.editArticle = async (req, res, next) => { try { const slugText = req.params.slug; if (!slugText) { return next(new Error("Slug is required")); } const isUpdated = await updateArticle(slugText, req.user._id, req.body.article); if (isUpdated) { return res.status(200).json({ article: isUpdated }); } return res.status(404).json({ error: "Article not found or not authorized" }); } catch (err) { next(err); } };
2. 修正测试代码(匹配控制器实际行为)
- 将模块
require移到文件顶部,避免缓存问题 - 移除错误的
updatedArticle断言,聚焦验证服务调用和响应行为
const articleServiceHelper = require("../../../services/article.service"); const { editArticle } = require("../../../controllers/article.controller"); const mockResponse = () => { return { status: jest.fn().mockReturnThis(), json: jest.fn().mockReturnThis() }; }; const mockNext = jest.fn(); const mockUser = { _id: "6368ad4347dkhs" }; const mockUpdatedArticle = { title: "Updated article title", description: "Updated description", body: "Updated body" } describe("Unit tests for article.controller.js file", () => { describe("editArticle tests", () => { test("If user can edit an article", async () => { //Arrange const mockReq = { params: { slug: 'test-article-slug' }, user: mockUser, body: { article: mockUpdatedArticle } }; const mockRes = mockResponse(); jest.spyOn(articleServiceHelper, "updateArticle").mockResolvedValue(mockUpdatedArticle); //Act await editArticle(mockReq, mockRes, mockNext); //Assert expect(articleServiceHelper.updateArticle).toHaveBeenCalledTimes(1); expect(articleServiceHelper.updateArticle).toHaveBeenCalledWith( mockReq.params.slug, mockUser._id, mockReq.body.article ); expect(mockRes.status).toHaveBeenCalledWith(200); expect(mockRes.json).toHaveBeenCalledWith({ article: mockUpdatedArticle }); expect(mockNext).not.toHaveBeenCalled(); }); }); });
3. 临时应急方案(不修改控制器的情况下)
如果暂时无法修改控制器,可以调整mock让res.json返回Promise,确保测试能正确解析:
const mockResponse = () => { return { status: jest.fn().mockReturnThis(), json: jest.fn().mockResolvedValue(true) }; };
此方案仅为临时绕过问题,建议优先采用前两种彻底修正方案。
内容的提问来源于stack exchange,提问作者Nilesh Sanyal
相关产品推荐
相关产品推荐

