模拟Golang Lambda功能单元测试的AWS API Gateway请求和DynamoDB

时间:2019-01-16 09:49:13

标签: amazon-web-services unit-testing go

设置

  • Windows 10
  • 转到v1.10.3
  • aws cli v1.16.67

我要做什么

测试使用golang编写的AWS Lambda函数。该函数接受来自API Gateway的请求,然后对DynamoDB进行处理。以下内容大部分摘自this文章(我是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)
}

问题

我想编写一组单元测试来测试此功能。在我看来,我要做的第一件事是模拟API网关请求和DynamoDB表,但我不知道如何执行此操作。

问题

  1. 我应该使用一个模拟框架吗?
  2. 如果有人知道任何有关此主题的文档,可以指出吗? (我的Google技能尚未透露)

谢谢

6 个答案:

答案 0 :(得分:4)

我的方法是在指针接收器中传递依赖项(因为处理程序的签名受到限制)并使用接口。每个服务都有相应的接口。对于dynamodb-dynamodbiface。所以在你自己的lambda中 您需要定义一个接收器:

    <body>
        "stuff & stuff & more stuff"
        <script src = "yourSourceFileHereIf_Its_A_SeparateFile.js"></ script>
    </body>

将main更改为:

type myReceiver struct {
    dynI dynamodbiface.DynamoDBAPI
}

将处理程序更改为

func main() {

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

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

以及随后对dynamodb API的所有调用都必须通过接口进行:

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

然后在测试函数中,您需要模拟响应:

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

答案 1 :(得分:1)

答案 2 :(得分:0)

请检查在docker中运行dynamo-db是否不会帮助您实施测试。

检查:connecting AWS SAM Local with dynamodb in docker

您也可以很容易地将事件传递给测试中的处理程序。

答案 3 :(得分:0)

虽然模拟是一个可行的选择,但您也可以考虑使用专用的AWS帐户进行e2e测试,找到一些示例,包括dynamodb和api网关

lambda_e2e

答案 4 :(得分:0)

除了Adrian's answer

看看LocalStack。它提供了一个易于使用的测试/模拟框架,用于通过在本地计算机或Docker中扩展与AWS兼容的API来开发与AWS相关的应用程序。它支持两打AWS API,其中包括DynamoDB和Lambda。确实是功能测试的绝佳工具,无需为此使用单独的AWS环境。

答案 5 :(得分:0)

这个问题现在有点老了,但是您可以运行dynamodb locally并使用此aws-lambda-go-test模块,该模块可以在本地运行lambda,并且可以用来测试来自lambda的实际响应

我公开并升级了此模块的完整信息