3

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? i.e. 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();
            });

        });
});
John Rotenstein
  • 241,921
  • 22
  • 380
  • 470
mi10
  • 213
  • 3
  • 14
  • I am trying to do the same thing (mock Firehose) and seeing the same results (mock not called, only the real service). In the aws-sdk-mock docs, their test code uses var AWS = require('aws-sdk-mock'), not AWSMock. I don't know how this works under the hood, but if it works by replacing the variable, then the name could matter. – mmorrisson Apr 12 '17 at 19:33
  • So I am not alone. :-) :-( Hope to hear from people who have it working. Or hope to hear from AWS devs... By the way, name of the variable should not change the outcome. I am sure you tried AWS and it didn't work as well. – mi10 Apr 13 '17 at 22:39

1 Answers1

2

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

mi10
  • 213
  • 3
  • 14