Testing Serverless Apps Locally — AWS SAM Local vs LocalStack
Learn how to test serverless apps locally using AWS SAM Local and LocalStack’s samlocal. This tutorial walks through a Lambda Authorizer, highlighting SAM Local's function-level testing capabilities against LocalStack's high-fidelity emulation for testing serverless integrations.

Introduction
AWS Serverless Application Model (SAM) is an open-source framework for building and deploying serverless apps. It extends AWS CloudFormation with simpler syntax to define functions, APIs, and event sources. While it simplifies cloud deployments, true developer velocity comes from the local feedback loop, where you can code changes instantly without deploying to the cloud.
To solve this, developers often use local testing tools. Two tools address this: AWS SAM Local and LocalStack. SAM Local acts as a lightweight function emulator, used for rapidly testing the logic of your Lambda functions with test events. LocalStack, on the other hand, is a comprehensive cloud emulator that replicates the entire infrastructure stack, from API Gateway to IAM roles.
This tutorial will walk you through a SAM application with a custom Lambda Authorizer, where API Gateway requests are validated through the authorizer and policy evaluation before invoking the target Lambda. We will test it using both SAM CLI and LocalStack, comparing both approaches to understand their differences and use cases.
What is SAM Local?
When discussing local SAM development, the term “SAM Local” can be confusing because both LocalStack & SAM CLI offer a similar capability, each with a different purpose.
AWS SAM CLI’s sam local
is a function-level emulator for rapid Lambda logic testing. It runs function code in a Docker container mimicking the AWS Lambda runtime and provides a local API server to trigger functions. You can also generate and manage test events for supported AWS services and pass them to local resources.
Some of its core commands include:
sam local invoke
: Executes a one-time invocation of your Lambda function by running it inside a Docker container.sam local start-api
: Spins up a local HTTP server that emulates API Gateway, letting you test your API-triggered functions.
LocalStack’s samlocal
is a wrapper script that acts as the bridge that enables the SAM CLI to interact with a full cloud emulator. It intercepts standard SAM commands (like sam deploy
) and redirects them to your running LocalStack instance.
Instead of emulating a single function, samlocal
uses LocalStack to provision the entire stack, including API Gateway, IAM roles, Lambda functions, and other resources, just as CloudFormation would in AWS. This enables testing the full integration and configuration of all resources together.
Let’s examine both approaches in detail in this tutorial.
Prerequisites
localstack
CLI- Docker
- SAM CLI with
samlocal
wrapper - AWS CLI with the
awslocal
wrapper (optional) curl
Step 1: Setup the project
Our sample application consists of a simple “Hello World” Lambda function protected by an API Gateway with a custom Lambda authorizer.
1.1: Clone the repository
To begin, clone the sample repository from GitHub:
git clone https://github.com/localstack-samples/lambda-sam-authorizer.git
1.2: Understand the project
The project contains a pre-defined SAM template (template.yaml
) that provisions our resources: an API Gateway (MyApi
), a Lambda authorizer (MyAuthFunction
), and our main business logic function (MyFunction
).
AWSTemplateFormatVersion: '2010-09-09'Transform: AWS::Serverless-2016-10-31
Resources: MyApi: Type: AWS::Serverless::Api Properties: StageName: Dev Auth: DefaultAuthorizer: MyLambdaTokenAuthorizer Authorizers: MyLambdaTokenAuthorizer: FunctionArn: !GetAtt MyAuthFunction.Arn
MyFunction: Type: AWS::Serverless::Function Properties: CodeUri: ./src Handler: handler.hello Runtime: nodejs20.x Events: GetRoot: Type: Api Properties: RestApiId: !Ref MyApi Path: / Method: get
MyAuthFunction: Type: AWS::Serverless::Function Properties: CodeUri: ./src Handler: authorizer.auth Runtime: nodejs20.x
# Output API Gateway URL and use localhost.localstack.cloud instead of amazonaws.comOutputs: APIGWEndpoint: Description: "API Prod stage endpoint" Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.localhost.localstack.cloud/Dev/"
The authorizer (src/authorizer.js
) validates the Authorization token and returns IAM policies. It supports three main cases: allow, deny, and unauthorized, with the last using callback(“Unauthorized”) to return a 401 error.
module.exports.auth = (event, context, callback) => { var token = event.authorizationToken; switch (token) { case 'allow': callback(null, generatePolicy('user', 'Allow', event.methodArn)); break; case 'deny': callback(null, generatePolicy('user', 'Deny', event.methodArn)); break; case 'unauthorized': callback("Unauthorized", generatePolicy('user', 'Deny', event.methodArn)); // Return a 401 Unauthorized response break; default: callback("Error: Invalid token", generatePolicy('user', 'Deny', event.methodArn)); // Return a 500 Invalid token response }};
The core business logic (src/handler.js
) is a simple function that, once authorized by API Gateway, returns a 200 OK response with the body “hello world”.
module.exports.hello = (event, context, callback) => { const response = { "statusCode": 200, "headers": {}, "body": 'hello world' } callback(null, response);};
1.3: Build the SAM project
Before running or deploying the application, it must be packaged with the SAM CLI. The sam build --use-container
command processes template.yaml
by pulling the required runtime images, analyzing & packaging each function, and copying the required files into the .aws-sam/build
directory.
Run the following command in your terminal:
sam build --use-container
With the application now built, it’s ready for local testing using both of our chosen methods.
Step 2: Testing with AWS SAM Local
First, we’ll look at the SAM CLI’s local testing capabilities. This function-focused approach provides rapid feedback on your code logic without provisioning the full cloud infrastructure.
2.1: Direct Function Testing with sam local invoke
The most direct way to test a Lambda function with SAM CLI is with sam local invoke
. This runs the function’s handler once inside a local Docker container that simulates the Lambda runtime. It serves as the serverless equivalent of a unit test.
Let’s invoke our main business logic function directly:
sam local invoke MyFunction
When you run the command, SAM CLI pulls the local Docker image, mounts the built code, and executes the handler. The output ends with the exact JSON response returned by your function:
{"statusCode": 200, "headers": {}, "body": "hello world"}
sam local invoke
is fast, giving instant feedback on your function’s logic, making it ideal for unit testing. However, this approach has certain limitations. It bypasses API Gateway and our authorizer entirely, so only the function handler is tested.
It also lacks service integration, so any calls to other services such as DynamoDB or S3 will fail since no emulation is provided for them. You’ll have to either mock them or point the service calls to AWS or a cloud emulator like LocalStack.
For validating API integration and authorizer flows, we need to move beyond isolated function execution.
2.2: API Emulation with sam local start-api
To test the function with its API Gateway trigger, use sam local start-api
. This starts a local web server that listens for HTTP requests, emulates API Gateway integration, and then invokes the mapped Lambda function.
Run the following command:
sam local start-api
You should see output indicating that the API is mounted at http://127.0.0.1:3000
.
No current session found, using default AWS::AccountId
AWS SAM CLI does not guarantee 100% fidelity between authorizers locallyand authorizers deployed on AWS. Any application critical behavior shouldbe validated thoroughly before deploying to production.
Testing application behaviour against authorizers deployed on AWS can be done using the sam sync command.
Mounting MyFunction at http://127.0.0.1:3000/ [GET]You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions,changes will be reflected instantly/automatically. If you used sam build before running local commands, you will need to re-run sam build for thechanges to be picked up. You only need to restart SAM CLI if you update your AWS SAM template2025-08-19 13:43:11 WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:30002025-08-19 13:43:11 Press CTRL+C to quit
Now, let’s test our authorizer’s logic with a series of curl
requests in a separate terminal.
First, let’s test the allow
case:
curl http://127.0.0.1:3000 -H "Authorization: allow"
It returns:
hello world
This works as expected. The authorizer allows the request, and our main function is executed.
Next, test the deny
case:
curl http://127.0.0.1:3000 -H "Authorization: deny"
It returns:
{"message":"User is not authorized to access this resource"}
This also works correctly. The authorizer returns a “Deny” policy, and the emulated API Gateway blocks the request.
sam local start-api
is useful for testing the link between HTTP requests and Lambda functions, but its API Gateway emulation is only a lightweight local web server. It handles requests but doesn’t replicate the AWS control plane, missing features like IAM role enforcement, detailed error responses, and full infrastructure validation.
To test these system-level interactions with higher accuracy, we need to emulate the entire infrastructure using LocalStack.
Step 3: Testing with LocalStack’s samlocal
Now, let’s switch to the infrastructure-centric approach. Instead of emulating a single function, we’ll deploy the full SAM stack to LocalStack, which provides a high-fidelity local emulation of CloudFormation, API Gateway, and other AWS services.
The samlocal
wrapper script redirects the SAM CLI’s AWS calls to the LocalStack instance, making the deployment process easy.
3.1: Start LocalStack
Before deploying, make sure LocalStack is up and running:
localstack start -d
3.2: Deploy the SAM Stack
Next, deploy the SAM stack:
samlocal deploy --guided
This command starts the guided deployment. You can press Enter to accept the default values for each prompt, which are then saved in a samconfig.toml
file for future deployments.
Configuring SAM deploy======================
Looking for config file [samconfig.toml] : Not found
Setting default arguments for 'sam deploy' ========================================= Stack Name [sam-app]: AWS Region [us-east-1]: #Shows you resources changes to be deployed and require a 'Y' to initiate deploy Confirm changes before deploy [y/N]: y #SAM needs permission to be able to create roles to connect to the resources in your template Allow SAM CLI IAM role creation [Y/n]: Y #Preserves the state of previously provisioned resources when an operation fails Disable rollback [y/N]: y Save arguments to configuration file [Y/n]: Y SAM configuration file [samconfig.toml]: SAM configuration environment [default]:
You’ll see SAM CLI creating a CloudFormation changeset and deploying the resources (IAM Roles, Lambdas, API Gateway) into your LocalStack container. The final output shows the CloudFormation output from the deployed stack, including the API Gateway URL:
CloudFormation outputs from deployed stack----------------------------------------------------------------------------------------------------------------------------------------------Outputs----------------------------------------------------------------------------------------------------------------------------------------------Key APIGWEndpointDescription API Prod stage endpointValue https://iqq4xphvy0.execute-api.localhost.localstack.cloud:4566/Dev/----------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - sam-app in us-east-1
3.3: Run the tests
Once deployed, LocalStack provides a unique URL for the API Gateway endpoint. You can capture it from the CloudFormation output by assigning it to a url
variable:
url=$(samlocal list stack-outputs --stack-name sam-app --output json --region us-east-1 | jq -r '.[0].OutputValue')echo $url
Now, repeat the curl tests against the new LocalStack endpoint.
curl ${url} -H "Authorization: allow"
It returns:
hello world
Let’s test an unauthorized request against the LocalStack endpoint.
curl ${url} -H "Authorization: unauthorized"
It returns:
{"message": "Unauthorized"}
This test goes beyond executing a single function. It validates that the entire SAM stack deploys successfully and that all resources, including API Gateway and IAM roles, are provisioned and integrated in the local environment.
Instead of testing code in isolation, you’re confirming the full system works as intended. This move from function emulation to full-stack simulation provides confidence that the application will behave correctly in the cloud.
Which approach is better?
The “better” approach depends on your development goal. Each tool serves a different purpose: SAM Local prioritizes speed, while LocalStack provides higher fidelity.
Use AWS SAM Local when:
- Your main focus is business logic inside Lambda functions.
- You’re running unit tests or isolated function tests.
- You want a lightweight, fast inner development loop (code → save → test).
- Your app has a few AWS dependencies, or you’re fine mocking interactions.
- You need high Lambda runtime fidelity via the same Docker images as production.
Use LocalStack’s samlocal
when:
- You need to test a full serverless application integration
- Your app depends on complex multi-service interactions (S3 → Lambda → DynamoDB)
- You’re running end-to-end tests or need shared team environments
- You want to validate SAM templates and resource permissions
- You need a realistic AWS control plane and service behavior emulation (IAM role enforcement, error handling, etc.)
Beyond deployment fidelity, developer experience features also matter. LocalStack offers built-in Lambda hot reloading, detecting code changes in sub-second intervals. SAM Local lacks this for local emulation, but sam sync
pushes updates directly to AWS for rapid cloud-side testing.
Both tools support remote debugging with IDE integration, but LocalStack also adds a UI with a Lambda Resource Browser (available on LocalStack Web App) for inspecting & managing functions along with detailed coverage documentation that shows local-to-AWS API parity.
Conclusion
You don’t need to choose a single tool. An effective serverless workflow combines both, using each where it’s strongest.
- For your inner development loop, where you need to test business logic for specific Lambda functions, use AWS SAM Local. The speed and simplicity provide near-instant feedback for rapid, iterative development.
- For your outer integration loop, deploy the stack to LocalStack. Run integration tests locally to validate IaC, IAM roles, and service interactions, catching subtle bugs through locally emulated infrastructure.
- For automating infrastructure tests, set up CI/CD pipelines to start LocalStack, deploy infrastructure, and run end-to-end tests. This prevents integration issues and misconfigurations from reaching production.
While SAM Local excels at quickly testing function code, production-ready serverless apps require higher confidence. By validating the entire infrastructure on LocalStack, you can emulate broader AWS services and interactions, ensuring your application behaves as expected when it matters most.