aws
Serverless computing on AWS #4 Unit testing
by Stuartpublished onLean manufacturing, pioneered by Toyota in the 90's, advocates quality checks of output as it leaves each machine in a production line. The objective is to pick-up flaws early and correct them in situ; thus avoiding the waste that would accumulate if they were allowed to flow through subsequent processes.
We software developers have adopted this approach. Our early stage quality checks are called Unit Tests. Below, I describe how I have incorporated unit testing into my Lambda functions.
Lambda function
My Lambda functions are written in Node.js (Javascript).
The following example handler.js file exposes a simple function: getUser():
'use strict';
var AWS = require('aws-sdk');
var Models = require('./models');
var Config = require("./config.json");
module.exports.AWS = function (aws) {
AWS = aws; // a bit of dependency injection for unit tests
}
module.exports.Config = function (config) {
Config = config;
}
module.exports.getUser = function (event, context, callback) {
try {
callback(null, { email: event.cognitoPoolClaims.email });
}
catch (err) {
callback(err, null);
}
}
The event object is available to the Lambda function at runtime. In our case the event object holds authentication details from the AWS Cognito service.
You'll notice above that there's a mechanism to override the AWS and Config properties in my handler module. This allows me to inject mock objects into our handler and to use these objects when I execute my unit tests.
Mocking frameworks exist for AWS: aws-sdk-mock is a good example. I had issues implementing it for DynamoDB. If you look at the outstanding issues for this library on GitHub you'll see that there are quite a few.
Creating my own simple mock objects didn't take long and it gave me confidence that unit test failures originated in my functions and not in the mocking framework. You don't want to be second-guessing your mocking framework when your unit test fails.
You don't want to be second-guessing your mocking framework when your unit test fails.
Here's an example of a mocked AWS object:
AWSMock = {};
AWSMock.DynamoDB = function(){
this.getItem =function(params, callback){
callback(null, {/* getItem test object goes here */})
}};
AWSMock.DynamoDB.DocumentClient = function(){
this.get =function(params, callback){
callback(null, {/* get test object goes here */})
},
this.scan =function(params, callback){
callback(null, {Items:[{/* scan test objects go here */}]});
},
this.query =function(params, callback){
callback(null, {Items:[{/* query test objects go here */}]});
}
}
var handler = require("./handler.js"); // where handler refers to the Lambda function module
handler.AWS(AWSMock);
Here's another function, that uses the DynamoDB service:
module.exports.getCalendarEntries = function (event, context, callback) {
var dynamoDb = new AWS.DynamoDB.DocumentClient();
dynamoDb.scan(
{ TableName: "fooddiary-calendarEntries-"
+ Config.stage }, function (err, data) {
// method should return a collection of Calendar Entries
let calendarEntries = new Array();
data.Items.forEach(element => {
calendarEntries.push(
new Models.CalendarEntry(element.username,
element.datetime, element.calendarEntry.foods,
element.calendarEntry.symptomSeverities));
});
if (err) {
callback(err, null);
} else {
callback(null, calendarEntries);
}
});
};
Happy testing!
Comments