简体   繁体   中英

Mocking putRecord on firehose not working with aws-sdk-mock

I am trying to mock putRecord method on AWS Firehose object but mocking is not succeeding. The code ends up calling the aws-sdk api on firehose object which talks with live aws service. What is wrong in below code? what needs to be changed to avoid this live service call and make mock to be effective?

Is there a way to send a thenable object and not just plain object as it does with callback below? ie someway to use something like callbackFunc that I defined there in the test code?

Also eventually I need to check if the mock did get called or not. How would I achieve that? Could I use sinon.stub in some manner to achieve that so that later on I can verify? How?

Here is code and test code portions... modified to simple form for this posting.

code that is part of a file say 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;

Here is my test code that is part of a file say 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();
            });

        });
});

Sharing the answer that I figured out, especially since another person (mmorrisson) was trying to do the same.

Essentially I added _setFirehoseInstance method to my logger class that would be called only from my test code which replaces the firehose instance (which in production code would have called new AWS.Firehose()) with my own simple mock class.

In my test code... let firehoseMock = {};

In beforeEach() create and set the mock to replace the actualfirehose instance and in afterEach() restore that.

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

And in the test code where we try to log, check if firehoseMock.putRecord was called or not...

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

});

In production code, in the logger class have the getter and setter for firehose instance.

/**
 * 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;
    }
}

This worked for me. when logger is calling firehose instance method in production, it will go to AWS service but in unit tests when I call log method, it calls the putRecord on the mock because I have replaced the firehose instance with mock. I can then appropriately test if putRecord on mock was called (using sinon.spy).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM