You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 20:57:25