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

使用Sinon Stub测试带中间件保护的Express.js路由遇阻

解决Express.js路由测试中Sinon Stub认证中间件失效的问题

我看到你在测试受isAdmin中间件保护的路由时遇到了Stub被忽略的问题,报错TypeError: Cannot read property 'user' of undefined,这说明原始的中间件仍在执行,而你的Stub根本没生效。问题根源在于Node.js的模块缓存机制,下面给你详细分析和解决办法:

问题原因

当你加载Express应用(server)时,它会导入路由模块,而路由模块已经提前导入并缓存了原始的isAdmin中间件引用。之后你在测试中Stub的只是测试文件里的auth.isAdmin,但路由里使用的还是缓存的原始中间件,所以Stub完全被忽略,原始中间件执行时找不到req.decoded.user就抛出了错误。

解决方案

方案1:清除模块缓存,强制重新加载模块

在beforeEach中先删除isAdmin和app的模块缓存,这样每次测试都会重新加载这些模块,路由就能使用Stub后的中间件了。修改你的beforeEach代码:

beforeEach(() => {
  // 删除模块缓存,避免复用之前加载的版本
  delete require.cache[require.resolve("../../../middlewares/isAdmin")];
  delete require.cache[require.resolve("../../../app")];

  // 重新加载isAdmin模块并Stub
  auth = require("../../../middlewares/isAdmin");
  sinon.stub(auth, "isAdmin").callsFake((req, res, next) => {
    console.log("Stubbed");
    return next();
  });

  // 重新加载server,此时路由会使用Stub后的中间件
  server = require("../../../app");
});

方案2:使用proxyquire替换模块导出(更灵活的方式)

如果清除缓存的方式不符合你的测试场景,可以用proxyquire工具直接替换中间件的导出。

首先安装依赖:

npm install proxyquire --save-dev

然后修改测试文件:

import chai from "chai";
import chaiHttp from "chai-http";
import sinon from "sinon";
import proxyquire from "proxyquire";
import { country } from "./addcountry-data";

let server;
const { expect } = chai;

chai.should();
chai.use(chaiHttp);

describe("Admin user can add country", () => {
  let stubbedIsAdmin;

  beforeEach(() => {
    // 创建Stub的isAdmin中间件
    stubbedIsAdmin = sinon.stub().callsFake((req, res, next) => {
      console.log("Stubbed");
      return next();
    });

    // 使用proxyquire加载app,替换isAdmin中间件
    server = proxyquire("../../../app", {
      "./middlewares/isAdmin": { isAdmin: stubbedIsAdmin }
    });
  });

  it("should allow user with admin role add a country", done => {
    chai.request(server)
      .post("/api/v1/admin/addcountry")
      .send(country)
      .end((err, res) => {
        expect(res).to.have.status(201);
        // 验证Stub确实被调用过
        expect(stubbedIsAdmin.calledOnce).to.be.true;
        done();
      });
  });
});

额外提示

  • 确保测试中没有其他地方提前加载app或isAdmin模块,否则缓存会干扰Stub效果。
  • 如果后续控制器需要req.decoded的值,你可以在Stub的中间件里给req.decoded赋值,比如:
    sinon.stub(auth, "isAdmin").callsFake((req, res, next) => {
      req.decoded = { user: { role: "Admin" } }; // 模拟解码后的用户信息
      return next();
    });
    

内容的提问来源于stack exchange,提问作者Fiyin Akinsiku

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 09:02:54