如何使用 Sinon 和 Proxyquire 模拟 Mongo 查找

How to mock Mongo find using Sinon and Proxyquire

我有下面的中间件class,我想对其进行单元测试:

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 mock 和存根 Role.find 方法的任何内部信息,以便我可以正确地对函数进行单元测试。

根据您的情况(单元测试),您根本不需要 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 例。祝你好运!