在函数内创建的JavaScript对象的模拟方法

问题描述

我编写了一个JavaScript函数,该函数从require()库创建一个对象,然后使用它。尝试为它编写测试时,这似乎使我感到麻烦,因为我似乎没有一种很好的方法来控制该对象并创建其方法的模拟方法来测试函数的行为。

我是否因为设计功能不佳而遇到这个问题?我来自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()的模拟未被dbService.getItem()使用,因为dbService.getItem()完全拥有自己对{{1}的依赖关系的实例化}对象。

我应该将DocumentClient传递到我的DocumentClient函数中,还是有更好的方法

解决方法

DI是最好的方法,它将使您的代码更易于测试,更好的可伸缩性以及分离模块。但是,如果要将TestRestTemplate模块作为依赖项,仍然可以对aws-sdk模块进行打桩。单元测试解决方案:

require

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);
  });
});