模拟 AWS API 网关请求和 DynamoDB for Golang Lambda 函数单元测试



Setup

  • 视窗 10
  • Go v1.10.3
  • AWS CLI v1.16.67

我想做什么

测试使用 golang 编写的 AWS Lambda 函数。该函数接受来自API Gateway的请求,然后对DynamoDB执行一些操作。以下大部分内容都摘自本文(我是 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. 如果有人知道任何有助于此主题的文档,请您指出来吗?(我的谷歌技能还没有透露)

谢谢

我这样做的方法是在指针接收器中传递依赖项(因为处理程序的签名是有限的)并使用接口。每个服务都有相应的接口。对于dynamodb - dynamodbiface .所以在你的情况下,在 lambda 本身您需要定义一个接收器:

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)

将处理程序更改为

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

并且所有对 dynamodb API 的后续调用都需要通过接口:

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

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


 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)

虽然这个问题有点老了。我也面临同样的问题,并在资源下方找到。把它放在这里是为了他人的利益。这些资源展示了如何在 golang 中编写 tastable lambda。

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

  2. https://dev.to/prozz/serverless-in-go-how-to-write-testable-lambdas-4925代码 - 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代码 - https://github.com/jboursiquot/shoutouts

请检查在

docker 中运行 dynamo-db 是否有助于您实现测试。

检查:在 docker 中将 AWS SAM Local 与 dynamodb 连接

您还可以非常轻松地将事件传递给测试中的处理程序。

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

lambda_e2e

除了阿德里安的回答:

看看LocalStack。它提供了一个易于使用的测试/模拟框架,用于通过在本地机器或 Docker 中启动与 AWS 兼容的 API 来开发与 AWS 相关的应用程序。它支持二十多个AWS API,DynamoDB和Lambda就是其中之一。它确实是一个很好的功能测试工具,而无需为此使用单独的 AWS 环境。

这个问题现在有点老了,但你可以在本地运行dynamodb并使用这个aws-lambda-go-test模块,它可以在本地运行lambda,并可用于测试lambda的实际响应

完全披露 我分叉并升级了这个模块

最新更新