在 Express 中测试中间件而无需创建重新创建服务器的简单方法?

问题描述 投票:0回答:3

我希望能够在每次测试的基础上存根我的中间件函数。正如here所阐述的,问题是我不能仅仅存根我的中间件函数,因为节点已经缓存了中间件函数,所以我不能存根它,因为我从一开始就创建了我的应用程序。

const request = require("supertest");
const { expect } = require("chai");
const sinon = require('sinon');
const auth = require ("../utils/auth-middleware")
const adminStub = sinon.stub(auth, "isAdmin").callsFake((req, res, next) => next());
const app = require("../app.js"); // As soon as I create this, the middleware isAdmin function becomes cached and no longer mutable

上面的工作原理与链接的SO答案中描述的解决方案相同,但我不喜欢这样,为了恢复存根或修改假的,我必须完全重新创建服务器。

我想知道是否有更好、更优雅的方法来解决 Node 首先缓存这些函数的事实

require
。我正在考虑使用
proxyquire
decache
但两者似乎都提供了解决方法而不是可持续的解决方案(尽管我在这里很可能是错的)。

javascript node.js mocha.js sinon
3个回答
11
投票

问题实际上与节点缓存模块无关 - 问题在于在最初创建服务器时谁存储了对中间件函数的引用。 在

isAdmin
d 模块的
require
方法被存根之后,缓存的版本会被存根,因此使用像
proxyquire
这样的工具只允许您在需要时需要模块的新版本(没有存根方法)由于某种原因。

如果您正在寻找的是调整已创建的 Express 服务器的特定中间件的行为,您需要一种通过引用来改变中间件函数的行为的方法。 希望 sinon 存根(以及其他存根,例如笑话提供的)能够做到这一点。但是,您仍然需要在创建快速服务器之前对模块进行存根,以便它存储对存根函数的引用。

示例实现可能如下所示:

const request = require("supertest");
const { expect } = require("chai");
const sinon = require('sinon');
const auth = require ("../utils/auth-middleware");

// store reference to original function in case you need it:
const originalIsAdmin = auth.isAdmin

// replace isAdmin method with a stubbed, but don't specify implementation yet
const adminStub = sinon.stub(auth, "isAdmin");

// init express server that relies on stubbed `auth.isAdmin` reference
const app = require("../app.js");

it('this test is using auth.isAdmin that just calls .next()', () => {
  // make middleware just pass
  auth.isAdmin.callsFake((req, res, next) => next());

  // ...
});

it('this test is using real auth.isAdmin implementation', () => {
  // make middleware call real implementation
  auth.isAdmin.callsFake(originalIsAdmin);

  // ...
});

1
投票

Sergey Lapin 解决方案对于我的单文件测试非常有用。 如果您正在运行多个测试文件(例如 mocha),并且其中一个像 Sergey Lapin 建议的那样对中间件进行了存根操作 - CallsFake 不起作用。

解决方案: test_config.js

const auth= require('../utils/auth-middleware');
const sinon = require('sinon');

const originalIsAdmin = auth.isAdmin;
const stubbedIsAuth = sinon.stub(auth, 'isAdmin');

const app = require("../app.js");

module.exports = {
    originalIsAdmin,
    stubbedIsAuth,
    app,
};

在文件 A.js 中您需要跳过 isAdmin:

const { app } = require('./test_config');
const auth= require('../utils/auth-middleware');

// before each test. No other .stub
beforeEach(function () {
            auth.isAdmin.callsFake((req, res, next) => next());
});

在文件 B.js 中您需要 isAdmin 的原始实现:

const { originalIsAdmin, app} = require('./test_config');

// before each test. No other .stub
beforeEach(function () {
            auth.isAdmin.callsFake(originalIsAdmin);
});

0
投票

我用打字稿做了下一个实现。我在 before 钩子中动态加载了

./app
(express)。在运行多个测试文件时,我遇到了 @Sebastian Wojdanowski 提到的同样问题。

// test.spec.ts
import middleware from  "../middleware/middle";

import sinon from "sinon";
import * as supertest from "supertest";

describe("user controller", ()=> {
  let request: supertest.SuperAgentTest;
  let myMiddle;

  before(() => {

    myMiddle = sinon.stub(middleware, "isAdmin");
    myMiddle.callsFake(async (_req, _res, next)=> next());

    import("../app").then(async app => {
      request = await supertest.agent(app.default);
    });
  });

  after(()=> {
    // This restores the stubs for the next test file. 
    // Without this, it returns the next error message 
    // Attempted to wrap isAdmin which is already wrapped
    sinon.verifyAndRestore();
  });

  describe("Next test suites", ()=> {
     
  });

});
© www.soinside.com 2019 - 2024. All rights reserved.