Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mock AWS API Gateway request and DynamoDB for Golang Lambda function unit Test

Setup

  • Windows 10
  • go v1.10.3
  • aws cli v1.16.67

What I'm trying to do

Test an AWS Lambda function written using golang. The function accepts a request from the API Gateway and then does some stuff with DynamoDB. Most of the below has been taken from this article (I'm a newbie with Go)

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "os"
    "regexp"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
)

var uuidRegexp = regexp.MustCompile(`\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b`)
var errorLogger = log.New(os.Stderr, "ERROR ", log.Llongfile)

type job struct {
    ID                string `json:"id"`
    ClientID          string `json:"clientId"`
    Title             string `json:"title"`
    Count             int    `json:"count"`
}

// CreateJobCommand manages interactions with DynamoDB
func CreateJobCommand(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

    if req.Headers["Content-Type"] != "application/json" {
        return clientError(http.StatusNotAcceptable) //406
    }

    newJob := new(job)
    err := json.Unmarshal([]byte(req.Body), newJob)

    // Ensure request has deserialized correctly
    if err != nil {
        return clientError(http.StatusUnprocessableEntity) //422
    }

    // Validate ID and ClientID attributes match RegEx pattern
    if !uuidRegexp.MatchString(newJob.ID) || !uuidRegexp.MatchString(newJob.ClientID) {
        return clientError(http.StatusBadRequest)
    }

    // Mandatory field check
    if newJob.Title == "" {
        return clientError(http.StatusBadRequest)
    }

    // Put item in database
    err = putItem(newJob) // putItem is defined in another file
    if err != nil {
        return serverError(err)
    }

    return events.APIGatewayProxyResponse{
        StatusCode: 201,
    }, nil
}

// Add a helper for handling errors. This logs any error to os.Stderr
// and returns a 500 Internal Server Error response that the AWS API
// Gateway understands.
func serverError(err error) (events.APIGatewayProxyResponse, error) {
    errorLogger.Println(err.Error())

    return events.APIGatewayProxyResponse{
        StatusCode: http.StatusInternalServerError,
        Body:       http.StatusText(http.StatusInternalServerError),
    }, nil
}

// Similarly add a helper for send responses relating to client errors.
func clientError(status int) (events.APIGatewayProxyResponse, error) {
    return events.APIGatewayProxyResponse{
        StatusCode: status,
        Body:       http.StatusText(status),
    }, nil
}

func putItem(job *job) error {

    // create an aws session
    sess := session.Must(session.NewSession(&aws.Config{
        Region:   aws.String("us-east-1"),
        Endpoint: aws.String("http://localhost:8000"),
    }))

    // create a dynamodb instance
    db := dynamodb.New(sess)

    // marshal the job struct into an aws attribute value object
    jobAVMap, err := dynamodbattribute.MarshalMap(job)
    if err != nil {
        return err
    }

    input := &dynamodb.PutItemInput{
        TableName: aws.String("TEST_TABLE"),
        Item:      jobAVMap,
    }

    _, err = db.PutItem(input)
    return err
}

func main() {
    lambda.Start(CreateJobCommand)
}

Problem

I want to write a set of unit tests to test this function. In my mind, the first thing I need to do is mock the API Gateway request and the DynamoDB table, but I've no idea how to do this.

Questions

  1. Is there a mocking framework I should be using?
  2. If anyone knows of any documentation that would help on this topic could you point it out please? (My Google skills haven't revealed any yet)

Thanks

like image 308
GreenyMcDuff Avatar asked Mar 04 '23 17:03

GreenyMcDuff


2 Answers

The way I do it is to pass dependencies in pointer receiver (since the handler's signature is limited) and use interfaces. Each service has corresponding interface. For dynamodb - dynamodbiface . So in your case in lambda itself you need to define a receiver:

type myReceiver struct {
    dynI dynamodbiface.DynamoDBAPI
}

change main to:

func main() {

    sess := session.Must(session.NewSession(&aws.Config{
        Region: aws.String("your region")},
    ))

    inj := myReceiver{
        dyn: dynamodb.New(sess),
    }
    lambda.Start(inj.CreateJobCommand)

change handler to

func (inj *myReceiver) CreateJobCommand(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error)

and all subsequent calls to dynamodb APIs would need to be to through interface:

    _, err = inj.dynI.PutItem(input)

Then in your test function you need to mock the responses:


 type mockDynamo struct {
    dynI dynamodbiface.DynamoDBAPI
    dynResponse dynamodb.PutItemOutput

} 

func (mq mockDynamo) PutItem (in *dynamodb.PutItemInput) (*dynamodb.PutItemOutput , error) {
    return &dynamodv.dynResponse, nil
}


        m1: = mockDynamo {

            dynResponse : dynamodb.PutItemOutput{
            
            some mocked output
        } 


        inj := myReceiver{
            dyn: m1,         
        }

     inj.CreateJobCommand(some mocked data  for APIGateway request)
like image 194
Izabela Skibinska Avatar answered Mar 09 '23 07:03

Izabela Skibinska


Although this question is bit old. I was also facing same issue and found below resources. Putting it here for the benefit of others. These resources show how to write tastable lambdas in golang.

  1. https://ewanvalentine.io/how-im-writing-serverless-services-in-golang-these-days/ Code - https://github.com/EwanValentine/serverless-api-example

  2. https://dev.to/prozz/serverless-in-go-how-to-write-testable-lambdas-4925 Code - https://dev.to/prozz/serverless-in-go-how-to-write-testable-lambdas-4925

  3. https://github.com/jboursiquot/serverless-go-orchestration-on-aws-course/wiki/Hands-On:-Writing-and-testing-your-Go-Lambdas Code - https://github.com/jboursiquot/shoutouts

like image 38
Jaydeep Patel Avatar answered Mar 09 '23 05:03

Jaydeep Patel