This is a sample implementation of AWS SAM local test using Localstack.
- docker
- AWS SAM cli
- Make development cycles faster by testing locally
- Keep the Lambda function code loosely coupled from the cloud platform
This code was implemented based on the test strategy introduced in the following slide by Takuto Wada https://speakerdeck.com/twada/testable-lambda-working-effectively-with-legacy-lambda
.
├── README.md
├── layer/ # lambda layer
│ └── nodejs/
│ └── node8/
│ ├── lib/
│ │ ├── core/
│ │ └── service/
│ ├── package.json
│ ├── tests/
│ │ ├── fixture/
│ │ └── unit/
│ └── yarn.lock
├── localstack/ # localstack docker compose
│ └── docker-compose.yml
└── sam-project/ # AWS SAM project
├── parseMailHeader/
├── script/
├── .env.dev
└── template.yaml
The Lambda Layer code can be placed in layer/
.
The code placed here is zipped by AWS SAM.
Localstack does not work if you have not set the dummy credentials.
aws configure --profile localstack
AWS Access Key ID: dummy
AWS Secret Access Key: dummy
Default region name [None]: us-east-1
Default output format [None]: json
Set the path where the dependency module has installed.
export NODE_PATH=(the pass to the repository)/layer/nodejs/node8/lib
cd localstack
docker compose up -d
cd layer/nodejs/node8
yarn install
yarn test
If you want to run test codes anytime you make changes:
yarn watch
SAM resources can be placed in sam-ploject/
.
The setting of the local environment should be described in .env-dev
.
# in .env-dev
export AWS_DEFAULT_REGION=us-east-1
export SAM_TEMPLATE=template.yaml
export S3_BUCKET_NAME_FOR_SAM_PACKAGE=(your s3 bucket for storing sam build artifacts)
export TEMPLATE_FILE=packaged.yaml
export AWS_PROFILE=(set your AWS profile)
export CFN_STACK_NAME=sam-localstack-example-dev
Create the S3 Buckets specified for S3_BUCKET_NAME_FOR_SAM_PACKAGE
and S3Bucket
in advance.
It is necessary to set the information of S3 Bucket that stores the material to be deployed in temlate.yaml.
cd sam-project/script
sh build.sh
cd sam-project/script
STAGE=dev sh deploy.sh
If you want to access the managed services from your Lambda functions, local testing will be difficult. Use dependency injection to avoid this.
The following code uses S3's client library, so it is difficult to test locally.
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
exports.lambdaHandler = async (event, context, callback) => {
const params = {
Bucket: bucket,
Key: key
};
const ret = await s3.getObject(params).promise();
const message = ret.Body.toString();
...
};
However, modifying the code as follows by injecting S3 client from outside, it is possible to replace S3's client library with test code and production code.
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const parseMailHeader = require('core/getS3Object');
exports.lambdaHandler = async (event, context, callback) => {
return await parseMailHeader({ s3, event, context, callback });
};
The lambdaHander function's test is still difficult as local, but parseMailHeader function is locally testable.
You can write test code as follows:
const AWS = require('aws-sdk');
const config = {
endpoint: 'http://localhost:4572',
s3ForcePathStyle: 'true',
}
const s3 = new AWS.S3(config);
...
it('successfully parse message', async () => {
const message = await parseMailHeader({s3, event, context, callback});
...
});
The endpoint http://localhost:4572
is a pseudo S3 service endpoint of the local stack.