【问题标题】:Sinon stub being skipped as node express middlewareSinon 存根作为 node express 中间件被跳过
【发布时间】:2019-05-20 01:01:00
【问题描述】:

我正在尝试测试特定路线的行为。即使我创建了一个存根,它也会继续运行中间件。我希望事件身份验证暂时通过。我知道此时它并不是真正的“单元”测试。我快到那里了。我还稍微简化了代码。这是要测试的代码:

const { rejectUnauthenticated } = require('../modules/event-authentication.middleware');

router.get('/event', rejectUnauthenticated, (req, res) => {
  res.sendStatus(200);
});

这是我要跳过的中间件:

const rejectUnauthenticated = async (req, res, next) => {
  const { secretKey } = req.query;
  if (secretKey) {
    next();
  } else {
    res.status(403).send('Forbidden. Must include Secret Key for Event.');
  }
};

module.exports = {
  rejectUnauthenticated,
};

测试文件:

const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
let app;
const authenticationMiddleware = require('../server/modules/event-authentication.middleware');

const { expect } = chai;
chai.use(chaiHttp);

describe('with correct secret key', () => {
  it('should return bracket', (done) => {
    sinon.stub(authenticationMiddleware, 'rejectUnauthenticated')
      .callsFake(async (req, res, next) => next());

    app = require('../server/server.js');

    chai.request(app)
      .get('/code-championship/registrant/event')
      .end((err, response) => {
        expect(response).to.have.status(200);
        authenticationMiddleware.rejectUnauthenticated.restore();
        done();
      });
  });
});

我尝试过其他类似的问题,例如:How to mock middleware in Express to skip authentication for unit test? 和这个:node express es6 sinon stubbing middleware not working 但我仍然从应该跳过的中间件中获得 403。我还在调试模式下运行了测试,所以我知道应该存根的中间件函数仍在运行。

这是我的代码存根的问题吗?这是 ES6 的问题吗?

我可以重组我的代码或测试以使其工作吗?

【问题讨论】:

    标签: node.js unit-testing mocha.js sinon chai


    【解决方案1】:

    存根代码确实存在问题。

    当您需要服务器文件时

    const app = require('../server/server.js');
    

    您的应用是使用整套中间件创建的,包括rejectUnauthenticated,对后者的引用存储在app 中。

    当你这样做时

    sinon.stub(authenticationMiddleware, 'rejectUnauthenticated')
      .callsFake(async (req, res, next) => next());
    

    您替换了authenticationMiddleware 模块的rejectUnauthenticated 导出方法,而不是对已存储的原始rejectUnauthenticated 的引用。

    解决方案是创建应用程序(即require('../server/server.js');您模拟导出的中间件方法后:

    const chai = require('chai');
    const chaiHttp = require('chai-http');
    const sinon = require('sinon');
    
    // don't create app right away
    let app;
    const authenticationMiddleware = require('../server/modules/event-authentication.middleware');
    
    const { expect } = chai;
    chai.use(chaiHttp);
    
    describe('with correct secret key', () => {
      it('should return bracket', (done) => {
        sinon.stub(authenticationMiddleware, 'rejectUnauthenticated')
          .callsFake(async (req, res, next) => next());
    
        // method is stubbed, you can create app now
        app = require('../server/server.js');
    
        chai.request(app)
          .get('/code-championship/registrant/event')
          .end((err, response) => {
            expect(response).to.have.status(200);
            authenticationMiddleware.rejectUnauthenticated.restore();
            done();
          });
      });
    });
    

    【讨论】:

    • 如果appauthenticationMiddleware 的导入路径与单元测试中使用的导入路径匹配,那么不需要在authenticationMiddleware 之后导入app 是否可以工作?
    • @LukeSchlangen 可能会受到您在同一进程中运行的其他测试的影响 - 如果任何其他测试需要 server.js,那么对它的引用(以及中间件方法)也会被节点缓存,并且在测试中再次要求只会返回缓存的实例。我的建议是单独运行测试,console.log Object.keys(require.cache) 在要求 server.js 之前确保它没有被缓存。
    • 是的!就是这样!当前文件中的所有测试都不是问题,但是需要server.js 文件的其他文件中的测试导致它中断!非常感谢!
    • @LukeSchlangen 很酷,很高兴为您提供帮助。还有一条评论-使用.restore(),您实际上并没有使所有内容恢复到初始状态,因为模块被缓存了-您最初遇到的问题相同,但相反。在这种情况下,您可能希望使用在单独的上下文中运行每个文件的测试运行器(afaik jest 这样做)或使用像 decache 这样的工具来每次重置节点模块缓存。
    • 我今天下午确实切换到了 Jest,代码更简单了。
    【解决方案2】:

    根据@Sergey 的建议,我确实改用了 Jest。至少对于这种特定情况,它大大简化了实现。对于那些感兴趣的人,这是最终结果:

    const express = require('express');
    const request = require('supertest');
    const registrantRouter = require('../server/routers/registrant.router');
    
    jest.mock('../server/modules/event-authentication.middleware');
    const { rejectUnauthenticated } = require('../server/modules/event-authentication.middleware');
    
    const initRegistrantRouter = () => {
      const app = express();
      app.use(registrantRouter);
      return app;
    };
    
    describe('GET /registrant', () => {
      test('It should 200 if event authentication passes', async (done) => {
        const app = initRegistrantRouter();
        rejectUnauthenticated.mockImplementation((req, res, next) => next());
        const res = await request(app).get('/event');
        expect(res).toHaveProperty('status', 200);
        done();
      });
      test('It should 403 if event authentication fails', async (done) => {
        const app = initRegistrantRouter();
        rejectUnauthenticated.mockImplementation((req, res) => res.sendStatus(403));
        const res = await request(app).get('/event');
        expect(res).toHaveProperty('status', 403);
        done();
      });
    });
    

    还要感谢这篇关于使用 Jest 测试快速应用的有用博文:https://codewithhugo.com/testing-an-express-app-with-supertest-moxios-and-jest/

    【讨论】:

      猜你喜欢
      • 2017-05-14
      • 2019-02-05
      • 1970-01-01
      • 1970-01-01
      • 2016-12-13
      • 1970-01-01
      • 2021-06-27
      • 1970-01-01
      • 2015-11-28
      相关资源
      最近更新 更多