【问题标题】:How to mock Mongo find using Sinon and Proxyquire如何使用 Sinon 和 Proxyquire 模拟 Mongo 查找
【发布时间】:2020-10-24 11:20:53
【问题描述】:

我有以下要进行单元测试的中间件类:

const jwt = require('jsonwebtoken');
const config = require('../config/auth.config.js');
const db = require('../models');
const User = db.user;
const Role = db.role;

isAdmin = (req, res, next) => {
  User.findById(req.userId).exec((err, user) => {
    if (err) {
      res.status(500).send({ message: err });
      return;
    }
    Role.find(
      {
        _id: { $in: user.roles }
      },
      (err, roles) => {
        console.log('Made it here Role');
        console.log(JSON.stringify(roles));
        console.log(roles);
        if (err) {
          console.log(err);
          res.status(500).send({ message: err });
          return;
        }

        for (let i = 0; i < roles.length; i++) {
          if (roles[i].name === 'admin') {
            next();
            return;
          }
        }

        res.status(403).send({ message: 'Require Admin Role!' });
        return;
      }
    );
  });
};

我能够模拟 User.findById,配置 jsonwebtoken,但我无法正确存根 Role.find(... 这是我目前使用 Mocha 和 Chai、Sinon 和 Proxyquire 的测试规范

const chai = require('chai');
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const mongoose = require('mongoose');
chai.should();
var expect = chai.expect;
describe('Verify AuthJWT class', () => {

  let mockAuthJwt;
  let userStub;
  let roleStub;
  let configStub;
  let jwtStub;
  let json;
  let err;
  let json1;
  let err1;

  before(() => {
    userStub = {
      exec: function (callback) {
        callback(err, json);
      }
    };
    roleStub = {
      find: function (query, callback) {
        console.log('Heree');
        return callback(err1, json1);
      }
    };
    configStub = {
      secret: 'my-secret'
    };
    jwtStub = {
      verify: function (token,
        secretOrPublicKey, callback) {
        return callback(err, json);
      }
    };
    mockAuthJwt = proxyquire('../../../middlewares/authJwt.js',
      {
        'User': sinon.stub(mongoose.Model, 'findById').returns(userStub),
        'db.role': sinon.stub().returns(roleStub),
        '../config/auth.config.js': configStub,
        'jsonwebtoken': jwtStub
      }
    );
  });
describe('isAdmin function', () => {
    it('should Pass when user is Admin', (done) => {
      err = null;
      json = { roles: ['5ef3bd3f4144ae5898347e4e'] };
      err1 = {};
      json1 = [{ _id: '5ef3bd3f4144ae5898347e4e', name: 'admin', __v: 0 }];
      let fakeRes = {
        status: sinon.stub().returnsThis(),
        send: sinon.stub()
      };
      let fakeReq = {
        body: { userId: '123', email: 'test@test.com', roles: ['admin'] }
      };

      let fakeNext = sinon.spy();
      mockAuthJwt.isAdmin(fakeReq, fakeRes, fakeNext);
      expect(fakeNext.calledOnce).to.be.true;
      console.log('Status ' + fakeRes.status.firstCall.args[0]);
      done();
    });

关于如何正确使用 proxyquire 模拟和存根 Role.find 方法的任何内部信息,以便我可以正确地对函数进行单元测试。

【问题讨论】:

    标签: unit-testing mongoose chai sinon proxyquire


    【解决方案1】:

    根据您的案例(单元测试),您根本不需要proxyquire。你只需要 chai 和 sinon。

    这是关于如何完成的简化示例。

    文件 middleware.js(仅作为示例文件名)

    // @file: middleware.js (This is line 1)
    const db = require('./models'); // Fake model.
    
    const isAdmin = (req, res, next) => {
      const User = db.user; // Define it inside.
      const Role = db.role; // Define it inside.
    
      User.findById(req.userId).exec((err1, user) => {
        if (err1) {
          res.status(500).send({ message: err1 });
          return;
        }
        Role.find({ _id: { $in: user.roles } }, (err2, roles) => {
          if (err2) {
            res.status(500).send({ message: err2 });
            return;
          }
    
          for (let i = 0; i < roles.length; i += 1) {
            if (roles[i].name === 'admin') {
              next();
              return;
            }
          }
    
          res.status(403).send({ message: 'Require Admin Role!' });
        });
      });
    };
    
    module.exports = { isAdmin };
    

    文件测试/规范:isAdmin.test.js

    const { expect } = require('chai');
    const sinon = require('sinon');
    
    // Load module under test.
    const middleware = require('./middleware');
    // Load module db to create stubs.
    const db = require('./models');
    
    describe('Verify AuthJWT class', function () {
      describe('isAdmin function', function () {
        it('should Pass when user is Admin', function (done) {
          // Fake result user findById.
          const fakeUser = { roles: ['5ef3bd3f4144ae5898347e4e'] };
          const fakeErrUser = null;
          // Create stub for User findById.
          const stubUserFindByID = sinon.stub(db.user, 'findById');
          stubUserFindByID.returns({
            exec: (arg1) => {
              // Inject fakeErrUser and fakeUser result here.
              arg1(fakeErrUser, fakeUser);
            },
          });
    
          // Fake result role find.
          const fakeRole = [{ _id: '5ef3bd3f4144ae5898347e4e', name: 'admin', __v: 0 }];
          const fakeErrRole = null;
    
          // Create stub for Role find.
          const stubRoleFind = sinon.stub(db.role, 'find');
          stubRoleFind.callsFake((arg1, arg2) => {
            // Inject fakeErrRole and fakeRole result here.
            arg2(fakeErrRole, fakeRole);
          });
    
          // Create fake response: empty object because no activity.
          const fakeRes = {};
          // Create fake request.
          // Note: I remove body property!
          const fakeReq = { userId: '123', email: 'test@test.com', roles: ['admin'] };
          // Create fake for next function (fake is sufficient).
          const fakeNext = sinon.fake();
    
          // Call function under test.
          middleware.isAdmin(fakeReq, fakeRes, fakeNext);
    
          // Verify stub user findById get called once.
          expect(stubUserFindByID.calledOnce).to.equal(true);
          // Make sure stub user findById called once with correct argument.
          expect(stubUserFindByID.calledOnceWith(fakeReq.userId)).to.equal(true);
    
          // Verify stub role find get called once.
          expect(stubRoleFind.calledOnce).to.equal(true);
          // Make sure stub role find called with correct argument.
          // Note: alternative style.
          expect(stubRoleFind.args[0][0]).to.deep.equal({
            // Query use fakeUser result.
            _id: { $in: fakeUser.roles },
          });
    
          // Finally for this case: make sure fakeNext get called.
          expect(fakeNext.calledOnce).to.equal(true);
    
          // Do not forget to restore the stubs.
          stubUserFindByID.restore();
          stubRoleFind.restore();
    
          done();
        });
      });
    });
    

    使用 nyc(检查覆盖率)和 mocha(测试运行程序)运行它。

    $ npx nyc mocha isAdmin.test.js --exit
    
    
      Verify AuthJWT class
        isAdmin function
          ✓ should Pass when user is Admin
    
    
      1 passing (7ms)
    
    ---------------|----------|----------|----------|----------|-------------------|
    File           |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    ---------------|----------|----------|----------|----------|-------------------|
    All files      |    77.27 |       50 |       60 |    76.19 |                   |
     middleware.js |    73.68 |       50 |      100 |    72.22 |    10,11,15,16,26 |
     models.js     |      100 |      100 |        0 |      100 |                   |
    ---------------|----------|----------|----------|----------|-------------------|
    $
    

    该测试用例仅涵盖成功条件(调用下一个函数)。我希望示例足够清楚,您可以继续创建测试用例,以根据我上面的示例完全覆盖函数 isAdmin。只剩下3个案例。祝你好运!

    【讨论】:

    • 一个更简单的问题,您将如何存根 User 构造函数? ``` const user = new User({ username: req.body.username, email: req.body.email, password: bcrypt.hashSync(req.body.password, 8) }); ```
    • 你不能这样做(存根构造函数)。 :) github.com/sinonjs/sinon/issues/1892
    猜你喜欢
    • 2021-09-29
    • 2018-08-12
    • 2021-10-02
    • 1970-01-01
    • 2018-08-07
    • 1970-01-01
    • 2015-07-10
    • 2016-04-21
    • 2017-08-02
    相关资源
    最近更新 更多