How can I test serverless lambda functions with constructors and dependencies?

How do I populate the constructor to pass the expected value for the constructed object to which it received the function call?

I am using serverless and I have a lambda function that has dependencies and runs every minute via a scheduled event. I want to focus on the behavior of a lambda function, so I want one of my tests to be this:> it removes messages from the message queue. The test will check that my queue received the function, dequeueMessages - and that it is. Here's my sample lambda:

module.exports = function(event, context, callback) {
  var queue = new Queue();

  queue.dequeueMessages(params).then(messages => {
    var client = new DataFetcher();

    return client.fetchData(messages).then(data => {
      var database = new Database();

      return database.persist(data);
    })
  }
}

      

I know there are other dependencies in there, but I just want to focus on getting the first test to pass, and I am struggling to issue new Queue

to make the assertion that the object constructed, the queue, called #dequeueMessages. I researched sinon , and have I have tests set up with mocha and tea, but I just don't know how to put all the tools together to make this a very simple test.

+3


source to share


2 answers


Since you only want to focus on the implementation details, this is pretty straightforward. I am attaching a working example based on your sample files, with a few tweaks just for completeness.

lib.js

This is a module that mimics third party dependencies - usually in code that should be something like AWS lib, etc.

class Queue{
  constructor(){

  }

  dequeueMessages(params ){
    console.log('dequeueMessages()', params);

    const messages = [];
    return Promise.resolve(messages);
  }

}

class DataFetcher{
  constructor(){

  }

  fetchData(messages ){
    console.log('fetchData()', messages);
    const data = {};
    return Promise.resolve(data);
  }
}

class Database{
  constructor(){

  }

  persist(data ){
    console.log('persist()', data);

    return Promise.resolve();
  }
}

module.exports.Queue = Queue;
module.exports.Database = Database;
module.exports.DataFetcher = DataFetcher;

      

lambda.js

Modified it a bit so that after completing the alert its caller.

'use strict';

const Queue = require('./lib').Queue;
const DataFetcher = require('./lib').DataFetcher;
const Database = require('./lib').Database;


module.exports = function(event, context, callback) {
  var queue = new Queue();

  var params = {};

  queue.dequeueMessages(params).then(messages => {
    var client = new DataFetcher();

    return client.fetchData(messages).then(data => {
      var database = new Database();

      database.persist(data).then(() => {
        callback(null, {});
      });
    });
  }).catch((error) => {
    callback(error);
  });
};

      



test.js

'use strict';

const chai = require('chai');
const sinon = require('sinon');
const SinonChai = require('sinon-chai');

var sinonStubPromise = require('sinon-stub-promise');
sinonStubPromise(sinon);

chai.use(SinonChai);
chai.should();

const lambda = require('./lambda');
const Queue = require('./lib').Queue;
const DataFetcher = require('./lib').DataFetcher;
const Database = require('./lib').Database;

context('Test', () => {


  beforeEach(() => {
    if (!this.sandbox) {
      this.sandbox = sinon.sandbox.create();
    } else {
      this.sandbox.restore();
    }
  });


  it('should pass the test',
    (done) => {

      const event = {};
      const ctx = {};

      const stubQueue = sinon.stub(Queue.prototype, 'dequeueMessages')
        .resolves(['1', '2']);
      const stubDataFetcher = sinon.stub(DataFetcher.prototype, 'fetchData')
        .resolves({});
      const stubDatabase = sinon.stub(Database.prototype, 'persist')
        .resolves();

      lambda(event, ctx, (error, result) => {

        try {
          // all stubs should have been called
          // one time each
          stubQueue.should.have.been.calledOnce;
          stubDataFetcher.should.have.been.calledOnce;
          stubDatabase.should.have.been.calledOnce;

          done();
        } catch (e) {
          done(e);
        }

      });
    });

});

      

The simplest thing you can do is simply stub out the required methods at the prototype level and control their behavior accordingly. In this example, we run 3 methods of interest that are used inside the lambda so that they return a fabricated result to take the test along the required path.

Finally, since the nature of lambda is async , we use it to use a callback so that we can gracefully execute our assertions upon completion.

Using this as a skeleton, you can use all the features Sinon has to offer to thoroughly test your implementation logic.

Last but not least, the article that @johni shared should really be used as a reference for more complex scenarios.

+2


source


I would recommend this article, which deals with the very important aspects of unit testing an AWS lambda function.



I accepted the way they do it.

+1


source







All Articles