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.
source to share
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.
source to share
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.
source to share