Unit testing a Serverless application (part 2)
On our previous post we started to shape a sample Serverless project to decouple it’s business logic, allowing us to write effective tests for it.
Let’s start adding a method to our TacoGallery class to save a record in a database. Given that we are using AWS, let’s use DynamoDB.
We add the AWS SDK to our project to be able to use DynamoDB, and UUID to help us create unique IDs for our records:
npm install — save aws-sdk uuid
And we update our TacoGallery class to look like this:
Here are some highlights about the code:
- Import both the AWS SKD and UUID
- Configure AWS to use the region “us-west1”
- On the class constructor, we create a DocumentClient and add it as a property to our class.
- Create the saveTaco which for now will take only two parameters: The name of the taco and its description. The method takes those parameters and creates a record on DynamoDB, returning a promise.
To make our code cleaner, we leverage the capability of the AWS SDK to create promises, instead of passing a callback function.
We add a new Lambda function to expose our new saveTaco method:
Unfortunately, Lambda still doesn’t support returning Promises directly, so we have to resolve ours and call the callback function inside the then block.
Please notice that we also have a catch block which also calls the callback function, but returns a 409-Conflict HTTP code, along with the actual error that we got back from the promise. This is in case that saveTaco throws any error, like missing required fields. With this, we make sure that the calllback is always called, even if the request fails.
In a production environment, you might want to filter what information is returned instead of passing the full err object, as it might provide sensitive information to the user.
How do we test this? We could configure our AWS account, create a database in DynamoDB and execute our Lambda. But since we want to save time and money, let’s keep things local. For that, we use dynalite, which we will install with the following command:
npm install dynalite — save-dev
Dynalite will run a local, in memory DynamoDB; perfect for our tests.
Let’s keep all the configuration related to Dynalite in its own file, to keep things in order.
This function is a little verbose, but here are the highlights:
- Create an instance of Dynalite
- We configure the AWS SDK to point to http://localhost:4567, which is where Dynalite will start
- Using the AWS SDK, create a database “TacoGallery”, and configure it to expect two required fields: id and name.
Using this utility function, we can set up our new Mocha test:
In the before block, we call our utility function to setup the Dynalite server and database, adding a small timeout just to make sure Dynalite has enough time to start.
Our new test is simple: we create an event object, with the values to insert a new Taco record in DynamoDB, a callback to test the result of our function and we pass both to our new Lambda function.
We are not passing done, even when our test is asynchronous. Also, why do we wrap our test contents in a new Promise?
Mocha allows us to return Promises in tests. If the promise resolves correctly, the test passes. If the Promise fails and it’s rejected, the test will fail.
Wrapping our test with a Promise allows us to use assertions if we need. Assertions don’t play well with the done function, as you need to use try-catch blocks to handle the error thrown by a failed assertion and call done afterwards. The Promise keeps our test clean and understandable.
Let’s run our tests to confirm everything is working:
mocha handler.test — compilers js:babel-core/register
We don’t have a lot of assertions to clearly confirm the tests are doing something. If you want more information, just delete in your test the “name” (“name”:”Al pastor”) property from the event.body object. Since we assigned this field as a sorting key in our table declaration, the insert will fail:
Our Serverless application as it is allows us to start coding in separate layers: All the Lambda specific logic goes into handler.js, your business logic into the TacoGallery.js file, and you local/test database configurations in TestUtils.util.js.
Without changes, handler.js and TacoGallery.js are fully functional and can be deployed into AWS (assuming you create the DynamoDB instance beforehand). This is because we loosely follow a decorator pattern in the way we configure our test Dynalite database, adding a layer of configuration to AWS SDK inside the test files.
Having set up our development project, start writing more tests and build the business logic to pass those tests, TDD style.
One last thing that doesn’t look quite right in this example: There is a strong coupling between our business logic and the AWS SDK. In next posts we will see a way to break that coupling, allowing us to reuse our business logic if we want to move to a different serverless provider.