如何使用 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 例。祝你好运!
我有下面的中间件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 例。祝你好运!