如何Mock AWS SDK测试Go语言实现的S3文件列表API端点
解决方案
核心问题原因
你的listFiles handler内部直接调用GetAWSClient()获取真实AWS客户端,硬编码的依赖导致测试时无法替换为Mock实例。
改造步骤
1. 解耦S3客户端依赖(修改业务代码)
你已经定义了S3ListObjectsAPI接口,我们通过依赖注入的方式将客户端传入handler,不要在handler内部直接获取真实客户端:
// 你已经定义好的接口,确保方法签名和S3 SDK的ListObjectsV2一致 type S3ListObjectsAPI interface { ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) } // NewListFilesHandler 接收S3客户端接口,返回实际的handler函数 func NewListFilesHandler(client S3ListObjectsAPI) http.HandlerFunc { return func(w http.ResponseWriter, _ *http.Request) { bucket := aws.String(BUCKETNAME) input := &s3.ListObjectsV2Input{ Bucket: bucket, } // 直接用传入的client调用方法,不需要再调用全局GetAWSClient resp, err := client.ListObjectsV2(context.TODO(), input) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } msg, _ := json.Marshal(resp.Contents) w.WriteHeader(200) w.Write(msg) return } } // 原有服务启动代码修改,传入真实AWS客户端 func handleRequest() { realS3Client := GetAWSClient() myRouter := mux.NewRouter().StrictSlash(true) // 注册路由时传入真实客户端构造的handler myRouter.HandleFunc("/listFiles", NewListFilesHandler(realS3Client)).Methods("GET") c := cors.Default() handler := c.Handler(myRouter) log.Fatal(http.ListenAndServe(":8080", handler)) }
如果你之前封装了GetObjects函数,需要将其参数修改为接收S3ListObjectsAPI接口类型,不要直接用s3.Client实体类型,确保Mock实例可以正常传入。
2. 调整测试逻辑,传入Mock客户端
测试时直接将你编写的Mock客户端传入handler构造函数,再用httptest发起请求即可,全程不会触发真实AWS调用:
func TestGetObjectFromS3(t *testing.T) { cases := []struct { client func(t *testing.T) S3ListObjectsAPI expect []byte expectStatus int }{ { client: func(t *testing.T) S3ListObjectsAPI { return mockListObjectsAPI(func(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) { t.Helper() if params.Bucket == nil { t.Fatal("expect bucket to not be nil") } if e, a := "fooBucket", *params.Bucket; e != a { t.Errorf("expect %v, got %v", e, a) } // 这里根据你的测试需要返回对应的Contents内容 return &s3.ListObjectsV2Output{ Name: aws.String("fooBucket"), Contents: []types.Object{ {Key: aws.String("test1.txt"), Size: aws.Int64(123)}, {Key: aws.String("test2.txt"), Size: aws.Int64(456)}, }, }, nil }) }, // 预期的响应JSON,和上面返回的Contents序列化结果一致即可 expect: []byte(`[{"Key":"test1.txt","Size":123},{"Key":"test2.txt","Size":456}]`), expectStatus: http.StatusOK, }, // 可添加错误场景的测试用例,比如Mock返回错误校验接口错误处理逻辑 } for i, tt := range cases { t.Run(strconv.Itoa(i), func(t *testing.T) { // 用Mock客户端构造handler testHandler := NewListFilesHandler(tt.client(t)) // 构造测试请求和响应记录器 req := httptest.NewRequest("GET", "/listFiles", nil) w := httptest.NewRecorder() // 执行请求 testHandler.ServeHTTP(w, req) // 校验响应状态码 if w.Code != tt.expectStatus { t.Errorf("expect status %d, got %d", tt.expectStatus, w.Code) } // 校验响应内容 respBody, _ := io.ReadAll(w.Result().Body) w.Result().Body.Close() if !bytes.Equal(respBody, tt.expect) { t.Errorf("expect body %s, got %s", string(tt.expect), string(respBody)) } }) } }
补充说明
- 所有依赖外部服务的逻辑都可以通过依赖注入的方式解耦,测试时替换为Mock实现即可完全隔离外部依赖
- 可以根据测试需要添加更多用例,比如Mock返回错误、返回空列表等场景,校验接口的异常处理逻辑是否符合预期
内容的提问来源于stack exchange,提问作者Loren
相关产品推荐
相关产品推荐

