Chapter 10: Testing Your Serverless Application
- Write a unit test for the
UpdateMovie
Lambda function.Answer:
package main import ( "testing" "github.com/stretchr/testify/assert" "github.com/aws/aws-lambda-go/events" ) func TestUpdate_InvalidPayLoad(t *testing.T) { input := events.APIGatewayProxyRequest{ Body: "{'name': 'avengers'}", } expected := events.APIGatewayProxyResponse{ StatusCode: 400, Body: "Invalid payload", } response, _ := update(input) assert.Equal(t, expected, response) } func TestUpdate_ValidPayload(t *testing.T) { input := events.APIGatewayProxyRequest{ Body: "{\"id\":\"40\", \"name\":\"Thor\", \"description\":\"Marvel movie\", \"cover\":\"poster url\"}", } expected := events.APIGatewayProxyResponse{ Body: "{\"id\":\"40\", \"name\":\"Thor\", \"description\":\"Marvel movie\", \"cover\":\"poster url\"}", StatusCode: 200, Headers: map[string]string{ "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, } response, _ := update(input) assert.Equal(t, expected, response) }
- Write a unit test for the
DeleteMovie
Lambda function.Answer:
package main import ( "testing" "github.com/stretchr/testify/assert" "github.com/aws/aws-lambda-go/events" ) func TestDelete_InvalidPayLoad(t *testing.T) { input := events.APIGatewayProxyRequest{ Body: "{'name': 'avengers'}", } expected := events.APIGatewayProxyResponse{ StatusCode: 400, Body: "Invalid payload", } response, _ := delete(input) assert.Equal(t, expected, response) } func TestDelete_ValidPayload(t *testing.T) { input := events.APIGatewayProxyRequest{ Body: "{\"id\":\"40\", \"name\":\"Thor\", \"description\":\"Marvel movie\", \"cover\":\"poster url\"}", } expected := events.APIGatewayProxyResponse{ StatusCode: 200, Headers: map[string]string{ "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, } response, _ := delete(input) assert.Equal(t, expected, response) }
- Modify the
Jenkinsfile
provided in previous chapters to include the execution of automated unit tests.
Answer: Note the usage of go test
command in the Test stage:
def bucket = 'movies-api-deployment-packages' node('slave-golang'){ stage('Checkout'){ checkout scm } stage('Test'){ sh 'go get -u github.com/golang/lint/golint' sh 'go get -t ./...' sh 'golint -set_exit_status' sh 'go vet .' sh 'go test .' } stage('Build'){ sh 'GOOS=linux go build -o main main.go' sh "zip ${commitID()}.zip main" } stage('Push'){ sh "aws s3 cp ${commitID()}.zip s3://${bucket}" } stage('Deploy'){ sh "aws lambda update-function-code --function-name FindAllMovies \ --s3-bucket ${bucket} \ --s3-key ${commitID()}.zip \ --region us-east-1" } } def commitID() { sh 'git rev-parse HEAD > .git/commitID' def commitID = readFile('.git/commitID').trim() sh 'rm .git/commitID' commitID }
- Modify the
buildspec.yml
definition file to include the execution of unit tests, before pushing the deployment package to S3 using AWS CodeBuild.Answer:
version: 0.2 env: variables: S3_BUCKET: "movies-api-deployment-packages" PACKAGE: "github.com/mlabouardy/lambda-codepipeline" phases: install: commands: - mkdir -p "/go/src/$(dirname ${PACKAGE})" - ln -s "${CODEBUILD_SRC_DIR}" "/go/src/${PACKAGE}" - go get -u github.com/golang/lint/golint pre_build: commands: - cd "/go/src/${PACKAGE}" - go get -t ./... - golint -set_exit_status - go vet . - go test . build: commands: - GOOS=linux go build -o main - zip $CODEBUILD_RESOLVED_SOURCE_VERSION.zip main - aws s3 cp $CODEBUILD_RESOLVED_SOURCE_VERSION.zip s3://$S3_BUCKET/ post_build: commands: - aws lambda update-function-code --function-name FindAllMovies --s3-bucket $S3_BUCKET --s3-key $CODEBUILD_RESOLVED_SOURCE_VERSION.zip
- Write a SAM template file for each Lambda function implemented in previous chapters.
Answer: The following is a SAM template file for the FindAllMovies
Lambda function; the same resources can be used to create other functions:
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Parameters: StageName: Type: String Default: staging Description: The API Gateway deployment stage Resources: FindAllMovies: Type: AWS::Serverless::Function Properties: Handler: main Runtime: go1.x Role: !GetAtt FindAllMoviesRole.Arn CodeUri: ./findall/deployment.zip Environment: Variables: TABLE_NAME: !Ref MoviesTable Events: AnyRequest: Type: Api Properties: Path: /movies Method: GET RestApiId: Ref: MoviesAPI FindAllMoviesRole: Type: "AWS::IAM::Role" Properties: Path: "/" ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "sts:AssumeRole" Principal: Service: - "lambda.amazonaws.com" Policies: - PolicyName: "PushCloudWatchLogsPolicy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: "*" - PolicyName: "ScanDynamoDBTablePolicy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - dynamodb:Scan Resource: "*" MoviesTable: Type: AWS::Serverless::SimpleTable Properties: PrimaryKey: Name: ID Type: String ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 MoviesAPI: Type: 'AWS::Serverless::Api' Properties: StageName: !Ref StageName DefinitionBody: swagger: 2.0 info: title: !Sub API-${StageName} paths: /movies: x-amazon-apigateway-any-method: produces: - application/json x-amazon-apigateway-integration: uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${FindAllMovies.Arn}:current/invocations" passthroughBehavior: when_no_match httpMethod: POST type: aws_proxy