3

我正在尝试在 AWS Firehose 对象上模拟 putRecord 方法,但模拟没有成功。代码最终在与实时 aws 服务对话的 firehose 对象上调用 aws-sdk api。下面的代码有什么问题?需要更改哪些内容才能避免此实时服务调用并使模拟有效?

有没有一种方法可以发送一个 thenable 对象,而不仅仅是像下面的回调那样发送普通对象?即以某种方式使用我在测试代码中定义的回调函数之类的东西?

最后我还需要检查模拟是否被调用。我将如何实现这一目标?我可以以某种方式使用 sinon.stub 来实现这一点,以便稍后我可以验证吗?如何?

这里是代码和测试代码部分...修改为简单的形式发布。

作为文件一部分的代码,比如 samplelogging.js。...

/*jshint strict:true */
/*jshint node:true */
/*jshint esversion:6 */
/*jshint mocha:true */
"use strict";

var Promise = require('bluebird');
var AWS = require('aws-sdk');
var uuid = require('uuid');

AWS.config.setPromisesDependency(Promise);

var Logger = {
    /**
     * Get's a AWS Firehose Instance 
     */
    getFirehoseInstance: function getFirehoseInstance() {
        if (!(this.firehose)) {
            this.firehose = new AWS.Firehose({apiVersion: "2015-08-04", region: "us-west-2"});
        }
        return this.firehose;
    },

    getLogger: function getLogger(options) {
        options = options || {};
        let self = this;

        self.class = options.class;
        self.firehose = self.getFirehoseInstance();

        return self;
    },


    logInfo: function logInfo(dataToLog, callback) { 
        this._log("info", dataToLog)        
            .then(function (data){
                if (callback) {
                    callback();
                }                
            });
        return;
    },

    /**
     * @api: private
     */
    _log: function _log(traceLevel, dataToLog) {

        return new Promise(function(resolve, reject) {
            var params = params || {};

            AWS.config.update({ logger: process.stdout });
            AWS.config.update({ retries: 3 });
            var recordParams = {
                type: params.type || 'LogEntry'
            };

            if (typeof dataToLog === 'string' || dataToLog instanceof String) {
                recordParams.data = { message: dataToLog };
            } else {
                recordParams.data = dataToLog;
            }

            recordParams.data.id = uuid.v1();
            recordParams.data.preciseTimestamp = Math.floor(new Date().getTime()/1000);
            recordParams.data.class = this.class;
            recordParams.data.traceLevel = traceLevel;

            var firehoseRecordParams = {
                DeliveryStreamName: "mystreamname",  //replace mystreamname with real stream name
                Record: {
                    Data: JSON.stringify(recordParams)+',\n'
                }
            };

            this.firehose.putRecord(firehoseRecordParams, function(err, recordId) {
                console.log("debug: recordId returned by putRecord = " + JSON.stringify(recordId));
                return resolve(recordId);
            });

        }.bind(this));
    }

};

module.exports = Logger;

这是我的测试代码,它是文件的一部分,比如 sampleloggingtest.js ...

var expect = require('chai').expect;
var Promise = require("bluebird");
var sinon = require("sinon");
var AWSMock = require('aws-sdk-mock');

describe.only("Logging tests", function () {

        it.only("Test AWS firehose API invoked", function (done) {

            let mylogger = Logger.getLogger({class: "Unit Test"});
            let firehoseInstance = mylogger.getFirehoseInstance();

            // want to have a callback function that returns a thenable object and not just an object. Not sure how to use it though with mock
            // so for now this is just code that shows what i intend to do.
            let callBackFunc = function( err, recordId) {
                    console.log("debug: returend from putRecord, recordId = " + JSON.stringify(recordId));
                    return Promise.resolve(recordId);
                };

            // calling mock as per the documentation at https://github.com/dwyl/aws-sdk-mock
            AWSMock.mock('Firehose', 'putRecord', function(params, callback) {
                console.log("debug: callback to putRecord to be called");                
                callback(null, {"RecordId": "12345"} );
            });

            // calling a method that should call firehose logging but our mock should intercept it - though it doesn't.
            mylogger.logInfo({ prop1: "value1" }, function(){
                console.log("debug: in the callback that was passed to logInfo...");
                done();
            });

        });
});
4

1 回答 1

2

分享我想出的答案,特别是因为另一个人(mmorrisson)也在尝试做同样的事情。

本质上,我将 _setFirehoseInstance 方法添加到我的记录器类中,该方法只能从我的测试代码中调用,它用我自己的简单模拟类替换了 firehose 实例(在生产代码中将调用 new AWS.Firehose())。

在我的测试代码中... let firehoseMock = {};

在 beforeEach() 创建并设置模拟以替换实际的firehose 实例,并在 afterEach() 中恢复它。

beforeEach(function (done) {
    logger = new Logger({class: "Unit Test"});
    firehose = logger.getFirehoseInstance();
    consoleMock = sinon.mock(console);

    firehoseMock.putRecord = function(params, callback) {
        let recordIdObj = {"RecordId": recordIdVal};
        callback(null, recordIdObj);
    };         
    logger._setFirehoseInstance(firehoseMock);    
    sinon.spy(firehoseMock, "putRecord");

    done();
});

afterEach(function (done) {
    firehoseMock.putRecord.restore();
    logger._setFirehoseInstance(firehose);
    consoleMock.restore();
    done();
});

在我们尝试记录的测试代码中,检查是否调用了 firehoseMock.putRecord ......

it("Test AWS firehose API invoked", function (done) {

    logger.setMode("production");
    logger.setEnvironment("test");

    logger.logInfo({ prop1: "value1" }, function(data){
        expect(firehoseMock.putRecord.calledOnce).to.equal(true);  // should have been called once
        expect(data.RecordId).to.equal(recordIdVal);  // should have matching recordId

        done();
    });

});

在生产代码中,记录器类中有用于 firehose 实例的 getter 和 setter。

/**
 * Get's a AWS Firehose Instance 
 */
getFirehoseInstance() {
    if (!(this.firehose)) {
        this.firehose = new AWS.Firehose({apiVersion: Config.apiVersion, region: Config.region});
    }
    return this.firehose;
}

/**
 * Set a AWS Firehose Instance - for TEST purose 
 */
_setFirehoseInstance(firehoseInstance) {
    if (firehoseInstance) {
        this.firehose = firehoseInstance;
    }
}

这对我有用。当记录器在生产中调用 firehose 实例方法时,它会转到 AWS 服务,但在单元测试中,当我调用 log 方法时,它会调用 mock 上的 putRecord,因为我已经用 mock 替换了 firehose 实例。然后我可以适当地测试是否调用了 putRecord on mock(使用 sinon.spy)。

于 2017-05-09T18:30:37.797 回答