Serverless Testing Strategies

Testing a Node.js + AWS Lambda + API Gateway app

Adil H
5 min readJul 16, 2018

Serverless computing and FaaS (Function as a Service) are planned to grow massively over the next few years. And each major cloud provider already has an offering: AWS Lambda, Google Cloud Functions, Azure Functions… But what does it mean for us web developers ? How can we adapt our development workflow when moving from traditional server based applications to “serverless” ? Let’s explore the testing side of the story !

AWS Lambda was first introduced in November 2014

Whenever I’m experimenting with a new technology, one of the first questions that comes up is: how do I write automated tests ? I think testing is a very important aspect of any software project. After all, if a piece of software can’t be easily tested then how can it be maintainable ?

Luckily there are a few ways to test serverless apps. For this article, we will build a Node.js serverless app and we’ll use the Serverless Framework and mocha.js to write and run our tests. You can use the github repository I’ve prepared if you’d like to browse through the code as you’re reading this article.

A simple lambda function

To provide some patterns for serverless testing, we’ll build a simple lambda function “asyncConcat” which takes 2 string arguments, joins them together and returns the result. We’ll also build a corresponding API endpoint with AWS API Gateway. We’ll also write unit / integration tests for these components. Here is a flowchart of what we’ll be building :

Request/Response cycle for asyncConcat

The code

We’ll be using a top-down approach, and we’ll start by defining the http GET /asyncConcat endpoint in the serverless.yml file

asyncConcat endpoint

This tells API Gateway to handle http calls to the GET /asyncConcat endpoint and trigger the asyncConcat lambda function. Next we’ll define the asyncConcat lambda function under functions/asyncConcat.js:

The handler function is a simple javascript async function that checks the query parameters, calls asyncConcatService.concat and returns the result. The reason for not doing the actual concatenation in the handler is to keep it testable and easy to reason about:

I think lambda handlers, similarly to controller methods in an MVC web app, should only orchestrate the business logic and handle responses, but the actual business logic should be delegated to a service method defined elsewhere. If you’re not familiar with this coding style I recommend you read up a bit on the Single Responsibility Principle.

Finally we define asyncConcatService.concat under lib/asyncConcatService.js:

If you are wondering why I made the concat method return the results asynchronously, it’s simply to illustrate how to test async methods/handlers (which could be pretty handy if we need to test database calls, sending emails, or other asynchronous tasks)

The tests

We’ll be defining 2 types of tests using Mocha as a test framework. But of course we could also have used Jest, Jasmine or any other javascript/node.js test framework.

Unit Tests

You might have noticed that the lambda handler is just a plain old javascript function ! So we can just test it by calling it with a mock event and context (You can read up on these lambda handler concepts in the AWS docs). We’ve defined 2 test cases for the handler:

  • The handler is called with a missing input (we need 2 string inputs to be able to concatenate them) and returns a response representing an HTTP 400 error code to the API gateway
  • The handler is called correctly and returns a response representing an HTTP 200 success code to the API gateway

The test code is defined under test/unit/functions/asyncConcat.test.js :

What we’re testing in the code above is just that the handler function receives the event object, handles it properly by checking for the “a” and “b” query parameters, calls asyncConcatService.concat and returns a proper response. We’ve used sinon.js to mock the call to asyncConcatService.concat and fake its response as that function will be tested independently in the next unit test.

The next test is defined under test/unit/lib/asyncConcatService.test.js and tests the actual business logic of joining two strings:

Integration Tests

Now that we’ve tested our code components independently, we want to see if the whole thing works. One way to do this is by writing an integration test which will simulate an entire request/response cycle as a black box: make HTTP API call -> check HTTP response.

A useful tool I came upon which helps to accomplish this is serverless-offline. The authors describe the tool this way: Emulate AWS λ and API Gateway locally when developing your Serverless project. Great ! we’ll use mocha hooks to boot serverless-offline during our tests and run the tests against it:

We can now write our integration test at test/integration/get-asyncConcat.test.js:

This last tests effectively sends an http request with two strings to the endpoint and tests that they are joined together in the response body.

All done ! I’ve also integrated Codeship with the github repo so we can make it part of our CI/CD pipeline and see our tests status in real time

green is good :)

While the serverless development tooling and ecosystem is still shaping up, we’ve seen that it is already possible to build reliable unit and integration tests. At least in some simple cases it’s possible but of course when we add more services such as AWS Cognito / SQS / SNS / Step functions / etc it’ll be more complicated to test the interfaces and the system as a whole, but using some of the patterns we’ve seen above creatively we can hopefully still write and run some tests !

I hope you found this post useful ! Please let me know if you have any questions or remarks about it.

--

--