Software testing increases application quality and reliability. It allows developers to find and fix software bugs, mitigate security issues, and simulate real user use cases.
It is an essential part of any application development.
Serverless is an amazing technology, almost magic-like. Serverless applications, like any other applications, require testing. However, testing Serverless applications differs from traditional testing and introduces new challenges.
In this blog post, you will learn why Serverless services introduce new testing challenges and my practical guidelines for testing Serverless services and AWS Lambda functions that mitigate these challenges.
Part twowill teach you how to write tests for your Serverless service. We will focus on Lambda functions and provide tips & tricks and code examples by writing tests for a real Serverless application.
In part three, you will learn to test asynchronous event driven flows that may or may not contain Lambda functions, and other non-Lambda-based Serverless services.
A complimentary Serverless service project that utilizes Serverless testing best practices can be found here.
Is Serverless Testing Any Different?
Well, yes, and by quite a lot.
In the ole' days, developers would run the code locally, and it would simulate the application behavior quite well. In most, cases, it was easy to run the code in the IDE, add breakpoints, and debug and If it worked locally, you had a lot of confidence that it would run just fine once deployed to production.
In Serverless applications, it's not that simple anymore.
Lambda Functions Perspective
Let's take a look at Lambda functions and the testing challenges they present.
AWS Lambda functions run on AWS infrastructure in an ephemeral container environment and require numerous function configurations.
These characteristics introduce numerous challenges that require a deeper understanding of the Lambda service:
The bottom line is that application code that works locally on your IDE is not guaranteed to even run in the AWS environment, let alone work correctly and as expected.
Serverless is More than Just Lambda Functions
Allen Helton, AWS Serverless Hero, defined Serverless quite nicely in his blog post:
"When I say serverless, I am generally referring to the services that developers use to build applications. Examples are AWS Lambda, EventBridge, DynamoDB, and Step Functions" - Allen Helton
Allen is right. You are getting top-class, state-of-the-art black box services that you piece into your architecture puzzle. The way I see it, Serverless is a synonym for event-driven architecture built on top of AWS-managed services and usually your goal is to pass an event through a chain of services until it reaches its final destination and form.
As such, Serverless architecture introduces new testing challenges:
However, despite all these challenges, there's light at the end of the tunnel.
Everything has a solution, so don't worry and read on.
Developer Experience and Quality Assurance Goals

The main goal is to test as much as possible to increase confidence in the quality of the application; however, a secondary goal is for the developer to have the best development experience in the Serverless domain.
The better the experience, the faster the development is.
So, as a Serverless application developer, I'd like to:
AWS Serverless & Lambda Testing Guidelines
Serverless Application Project Structure
Each application repository will contain three folders: service code, infrastructure as code folder (AWS CDK in this example), and tests folder. I believe that a Serverless developer must "own" the infrastructure and be able to define it and understand its architecture.
app.py is the entry point of the CDK application that deploys the infrastructure code and uploads the service folder with the Lambda functions.

Lambda Functions' Local Environment
We want to simulate the Lambda functions local environment and external dependencies in the IDE. In Python, which is my languagrs of choice for Lamda functions, we will install all dependencies to a local virtual environment.
As for dependency manager, you may choose between poetry and pipenv. I prefer poetry as its faster, so all the functions' dependencies will reside together in a single .toml file.
All Lambda functions in the project use identical library versions defined in the toml file. When building Lambda functions with external dependencies or lambda layers, build them according to the .toml file. Read more about it here.
Developer Deployment Independence
We want our developers to be able to work against real AWS accounts and resources without disrupting the work of their coworkers.
There are two possible solutions that I'm aware of:
In the first option, each developer deploys its application to its account. Each developer has a sandbox to play with, reducing the chance of reaching resource quota limits. However, multiple accounts increase account management overhead.
The second option is to have one 'dev' AWS account shared among all developers, but each developer deploys its application stack (CloudFormation stack) with a username prefix, thus removing a chance of conflicts and enabling the developers' complete freedom. In this option, there's more chance for reaching AWS resource quotas (in many cases, these are "soft" limits that you can increment for additional cost), but it's easier to manage from a company perspective.
Choose whatever option makes more sense to you.
Debug in IDE & Generated Event Inputs
I believe that debugging in the AWS console should always be the last resort.
It takes more time as you don't have breakpoints, so you resort to debugging with log printing, which translates to a bad experience overall.
A better developer experience would be to write a test that calls my Lambda function handler in my IDE, sends it a predefined event (that matches the schema of the Lambda integration), and verifies its side effects and response.
All locally run, simple and fast.
We will keep it simple; we will not use Lambda simulators nor SAM's local debug approach, or spin up local docker images, just plain old IDE tests ('pytest' in Python) with generated events and local breakpoints.
However, this begs the question, how do you generate these events? I'll leave that to part two that will cover this in detail. However, if you wish a spoiler, see an example here.
Don't Mock AWS Services Unless You Have To
The Pythonic motto library mocks AWS services, removing the need to deploy your application or pay for API calls against AWS services. Other programming languages have their motto implementation.
However, they all share one thing in common - they sound great on paper, but in my experience, you should only use them if you have to.
Let me explain why: one downside of using 'motto' is that when you use it to mock one AWS service, it forces you to mock all of them. You can't use ANY other real AWS services API calls. Another downside is that I've stumbled upon is instances where the motto response was different from the real deal. And yes, 'motto' can have bugs too.
So, you should use 'motto' in the following use case:
- It isn't easy to simulate a specific use case, or you need to know what the response would look like. For example, you want to use AWS organizations API to list all the accounts in the organization, and you want the organization to have 50 accounts in 3 hierarchies. Unless you keep this real AWS organization ready for use, a 'motto' mock is the only way to simulate this use case.
Debug in IDE & AWS API
Call real AWS services' API in IDE - continuing the last point, when using AWS SDK ('boto' for Python) in the Lambda, we will not mock it. I want to gain as much certainty and use actual AWS services as possible during my local debug sessions and tests.
Of course, this method means increased overall cost and is considered the major con of this method (and of the developer deployment independence) as your entire development team will deploy the infrastructure and use AWS API calls to interact with it.
Serverless has many pricing benefits (you pay is most cases only for actual runtime in millisecond for Lambdas), and AWS has a free tier, but eventually, it may add up.
In addition, many resources cost you money to deploy; KMS CMK, VPCs, and certificates come to mind.
However, in the grand scheme of things, using real APIs locally with a breakpoint and with ease increases team confidence in its work creates fewer conflicts and discrepancies between local code and code that runs on AWS accounts, and speeds up development. The minimal cost should make it worthwhile if you use pure Serverless AWS services.
Simulate API Failures
Patch AWS calls when you want to simulate exceptions and errors to test edge cases in your code and to increase line code coverage.
A fully working Serverless service template that embodies these guidelines can be found here, and its documentation here.
Part two will focus on this area with code samples.
Trigger an Event on Your AWS Account
In addition to debugging in local IDE, it's critical to also trigger the deployed application on AWS and verify that it works from beginning to start.
Use actual customer use cases and trigger the beginning of the event-driven architecture, whether it's a REST API call, an SNS message, or any other event.
The Lambda functions and Serverless resources you deployed will all work together from beginning to start on your AWS account.
These tests are the ultimate tests as they simulate real customer use cases and inputs on your AWS accounts.
Automate Everything
Write tests for all customer use cases and edge cases. Leave no stone unturned. Don't manually test your service. You want to gain confidence in your tests and empower your developer to have more responsibility, improved development speed, and confidence in their work. When you have good coverage, you are not afraid to push to production multiple times per day.
To Be Continued - Writing Tests for a Sample Serverless Application
Now that we have the basic guidelines, we will put them to use in the upcoming part two of the series, where I present the Serverless testing pyramid along with code examples and many tips & tricks.

##




