【问题标题】:Mocking methods of a JavaScript object created within a function在函数中创建的 JavaScript 对象的模拟方法
【发布时间】:2021-02-10 10:34:02
【问题描述】:

我编写了一个 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");
    });
});

很明显,dbService.getItem() 没有使用我创建的 dynamo.get() 模拟,因为 dbService.getItem() 完全拥有它自己对 DocumentClient 对象的依赖项的实例化。

我应该将DocumentClient 传递给我的getItem() 函数,还是有更好的方法?

【问题讨论】:

    标签: javascript mocking chai sinon stub


    【解决方案1】:

    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 |                   
    --------------|---------|----------|---------|---------|-------------------
    

    【讨论】:

    • 在这种情况下,将DocumentClient 传递给getItem() 函数是执行DI 的正确方法吗?所以签名会变成async function getItem(docClient)
    • @Ubunfu 你是对的。 getItem 函数应该依赖于抽象(接口),而不是具体
    猜你喜欢
    • 2017-11-06
    • 1970-01-01
    • 1970-01-01
    • 2017-01-15
    • 1970-01-01
    • 1970-01-01
    • 2020-03-26
    • 2014-12-06
    • 1970-01-01
    相关资源
    最近更新 更多