Jest测试报错:TypeError: 无法读取undefined的'_id'属性
问题根源分析
报错无法读取req.user._id(req.user为undefined)的核心原因是测试用例未模拟认证后的req.user对象。生产环境中createArticle接口依赖认证中间件(如JWT验证、Session校验)为req挂载用户信息,但测试时直接调用控制器函数,跳过了中间件流程,导致req.user不存在。
解决方案与测试实现
1. 测试用例中模拟req.user
在测试文件中,手动为req对象添加user属性,模拟认证后的请求上下文。以下是基于Jest的测试示例:
// article.controller.test.js const articleController = require('../article.controller'); const Article = require('../models/Article'); // 模拟Article模型的create方法 jest.mock('../models/Article'); describe('createArticle', () => { let req, res, next; beforeEach(() => { // 初始化模拟请求、响应、next函数 req = { body: { title: '测试文章', content: '测试内容' }, // 关键:添加模拟用户信息 user: { _id: 'mock-user-123' } }; res = { status: jest.fn().mockReturnThis(), json: jest.fn() }; next = jest.fn(); }); // 正常场景:成功创建文章 test('创建文章成功,返回201状态码与文章数据', async () => { // 模拟数据库返回结果 Article.create.mockResolvedValue({ _id: 'mock-article-456', title: '测试文章', content: '测试内容', author: 'mock-user-123', createdAt: new Date() }); await articleController.createArticle(req, res, next); // 验证逻辑正确性 expect(Article.create).toHaveBeenCalledWith({ title: '测试文章', content: '测试内容', author: req.user._id }); expect(res.status).toHaveBeenCalledWith(201); expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ title: '测试文章', author: 'mock-user-123' })); }); // 异常场景1:数据库操作失败 test('数据库创建失败时,调用next传递错误', async () => { const mockError = new Error('数据库连接异常'); Article.create.mockRejectedValue(mockError); await articleController.createArticle(req, res, next); expect(next).toHaveBeenCalledWith(mockError); expect(res.status).not.toHaveBeenCalled(); expect(res.json).not.toHaveBeenCalled(); }); // 异常场景2:请求参数缺失 test('必填字段缺失时,返回400状态码', async () => { // 模拟缺少title的请求 req.body = { content: '测试内容' }; await articleController.createArticle(req, res, next); expect(res.status).toHaveBeenCalledWith(400); expect(res.json).toHaveBeenCalledWith({ message: '标题与内容为必填项' }); expect(Article.create).not.toHaveBeenCalled(); }); // 异常场景3:未认证请求 test('未携带用户信息时,返回401状态码', async () => { // 移除req.user,模拟未认证请求 delete req.user; await articleController.createArticle(req, res, next); expect(res.status).toHaveBeenCalledWith(401); expect(res.json).toHaveBeenCalledWith({ message: '未授权:未检测到用户信息' }); expect(Article.create).not.toHaveBeenCalled(); }); });
2. 控制器代码添加防御性校验
在控制器中补充参数与用户信息校验,避免因异常请求导致崩溃:
// article.controller.js exports.createArticle = async (req, res, next) => { try { // 校验用户是否已认证 if (!req.user?._id) { return res.status(401).json({ message: '未授权:未检测到用户信息' }); } // 校验必填参数 const { title, content } = req.body; if (!title || !content) { return res.status(400).json({ message: '标题与内容为必填项' }); } // 创建文章 const article = await Article.create({ title, content, author: req.user._id }); res.status(201).json(article); } catch (err) { next(err); } };
3. 验证修复效果
运行测试命令(如jest article.controller.test.js),确认所有测试用例通过:
- 正常场景:返回201状态码与文章数据
- 数据库异常:正确传递错误到next函数
- 参数缺失:返回400状态码与错误提示
- 未认证请求:返回401状态码与未授权提示
内容的提问来源于stack exchange,提问作者Nilesh Sanyal
相关产品推荐
相关产品推荐

