我编写了一个JavaScript函数,它从require((d库创建一个对象,然后使用它。当我试图为它编写测试时,这似乎给我带来了麻烦,因为我似乎没有一个好的方法来控制该对象并创建其方法的mock来测试我的函数的行为。
我遇到这种情况是因为我设计的功能不好吗?我来自Java/Spring背景,所以我脑子里的声音都在尖叫";依赖性注入";。有没有比将函数所需的对象作为参数传递给它更好的方法呢?
示例功能:
// dbService.js
const AWS = require('aws-sdk');
function getItem() {
const dynamo = new AWS.DynamoDB.DocumentClient();
var params = {/* irrelevant */}
try {
return await dynamo.get(getParams).promise();
} catch (err) {
return err;
}
}
exports.getItem = getItem;
当dynamo.get()
成功返回或抛出错误时,当我试图编写测试来验证函数的行为时,我开始遇到阻塞。
示例测试(我一直用Sinon来嘲讽,用Chai来断言(:
// dbServiceTest.js
const sinon = require('sinon');
const dbService = require('dbService.js');
const expect = require('chai').expect;
describe('dbService: When database returns a record', function() {
let dbMock, dbServiceResp = null;
beforeEach(async function() {
dbMock = sinon.stub(dynamo, "get")
.returns({Item: "an item"});
dbServiceResp = await dbService.getItem("an item");
});
afterEach(function() {
dbMock.restore();
});
it('Should have expected value', function() {
expect(dbServiceResp.Item).to.be.equal("an item");
});
});
很明显,我创建的dynamo.get()
的mock没有被dbService.getItem()
使用,因为dbService.getItem()
完全拥有它自己对DocumentClient
对象的依赖关系的实例化。
我应该将DocumentClient
传递到getItem()
函数中,还是有更好的方法?
DI是最好的方法,它将使代码更易于测试,具有更好的可扩展性,并使模块解耦。但是,如果您想将require
模块作为依赖项,您仍然可以存根aws-sdk
模块。单元测试解决方案:
dbService.js
:
const AWS = require('aws-sdk');
async function getItem() {
const dynamo = new AWS.DynamoDB.DocumentClient();
var params = {
/* irrelevant */
};
try {
return await dynamo.get(params).promise();
} catch (err) {
return err;
}
}
exports.getItem = getItem;
dbService.test.js
:
const sinon = require('sinon');
const AWS = require('aws-sdk');
const expect = require('chai').expect;
describe('dbService: When database returns a record', function() {
afterEach(() => {
sinon.restore();
});
it('Should have expected value', async function() {
const mDynamo = { get: sinon.stub().returnsThis(), promise: sinon.stub().resolves({ Item: 'an item' }) };
const mDocumentClient = sinon.stub(AWS.DynamoDB, 'DocumentClient').returns(mDynamo);
const dbService = require('./dbService');
const actual = await dbService.getItem();
expect(actual.Item).to.be.equal('an item');
sinon.assert.calledOnce(mDocumentClient);
sinon.assert.calledWithExactly(mDynamo.get, {});
sinon.assert.calledOnce(mDynamo.promise);
});
it('should return error', async () => {
const mError = new Error('network');
const mDynamo = { get: sinon.stub().returnsThis(), promise: sinon.stub().rejects(mError) };
const mDocumentClient = sinon.stub(AWS.DynamoDB, 'DocumentClient').returns(mDynamo);
const dbService = require('./dbService');
const actual = await dbService.getItem();
expect(actual.message).to.be.eql('network');
sinon.assert.calledOnce(mDocumentClient);
sinon.assert.calledWithExactly(mDynamo.get, {});
sinon.assert.calledOnce(mDynamo.promise);
});
});
单元测试结果:
dbService: When database returns a record
✓ Should have expected value
✓ should return error
2 passing (26ms)
--------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
dbService.js | 100 | 100 | 100 | 100 |
--------------|---------|----------|---------|---------|-------------------