DEV Community

Adil H
Adil H

Posted on

Serverless Testing Strategies: Testing a Node.js + AWS Lambda + API Gateway app

I originally posted this article on Medium

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 2014AWS 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 serveless 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 asyncConcatRequest/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

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 :)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. Also if you have additional serverless testing strategies you’d like to contribute to the repo please open pull requests. And finally if you’re looking for help to implement serverless node.js apps I’m a freelancer and I’m always looking for new exciting projects. You can reach me on Twitter: @le_didil

Top comments (5)

Collapse
 
jonathanalberghini profile image
Jon Alberghini

Is there any way to get this running on windows? I added win-node-env.
C:\src\iam-auth-api>npm test

legacy-react-middleware-auth@1.0.0 test C:\src\iam-auth-api
NODE_ENV=test PORT=9100 ./node_modules/.bin/mocha --opts mocha.opts

'.' is not recognized as an internal or external command,
operable program or batch file.
Note: This command was run via npm module 'win-node-env'
npm ERR! Test failed. See above for more details.

Collapse
 
tommyxlos profile image
Thomas Los

Hi Adil,

Nice article. I was wondering, lets say your ci/cd pipeline is running a seperate acceptance environment, how would you handle the aws credentials to "acceptance test" on that environment?

Collapse
 
rehmans76 profile image
rehmans76

Hi Adil,
Great article, do you try using Pact based contract testing as lambda function is generally a micro service?

Collapse
 
didil profile image
Adil H

I haven't done that but it's an alternative

Collapse
 
wired00 profile image
Robert Bradshaw

just regarding the integration tests, is it possible to then mock api responses as normal with sinon etc when using sls offline?