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 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 asyncConcat
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:
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 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:
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
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)
Is there any way to get this running on windows? I added win-node-env.
'.' 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.
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?
Great article, do you try using Pact based contract testing as lambda function is generally a micro service?
I haven't done that but it's an alternative
just regarding the integration tests, is it possible to then mock api responses as normal with sinon etc when using sls offline?