如何为AWS的GetPresignedURL函数编写单元测试并解决空指针报错?
解决AWS S3预签名URL生成函数的单元测试空指针问题
问题背景
要测试的S3预签名URL生成函数如下:
// GetPresignedURL为S3中指定对象生成预签名URL func GetPresignedURL(svc s3iface.S3API, bucketName, objectKey string, expirationTime int) (string, error) { req, _ := svc.GetObjectRequest(&s3.GetObjectInput{ Bucket: aws.String(bucketName), Key: aws.String(objectKey), }) // 设置预签名URL的过期时间 url, err := req.Presign(time.Duration(expirationTime) * time.Minute) if err != nil { return "", err } return url, nil }
原测试代码尝试用mock的S3API验证逻辑,但执行req.Presign时触发空指针panic,报错信息:
--- FAIL: TestGetPresignedURL (0.00s) panic: runtime error: invalid memory address or nil pointer dereference [recovered] panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x6befe4] goroutine 6 [running]: testing.tRunner.func1.2({0x777ac0, 0xcf7850}) /usr/local/go/src/testing/testing.go:1396 +0x24e testing.tRunner.func1() /usr/local/go/src/testing/testing.go:1399 +0x39f panic({0x777ac0, 0xcf7850}) /usr/local/go/src/runtime/panic.go:884 +0x212 github.com/aws/aws-sdk-go/aws/request.(*Request).copy(0xc0003fe500) /home/gullu/go/pkg/mod/github.com/aws/aws-sdk-go@v1.49.16/aws/request/request.go:637 +0x124 github.com/aws/aws-sdk-go/aws/request.(*Request).Presign(0xc00002c360?, 0xc000124630?) /home/gullu/go/pkg/mod/github.com/aws/aws-sdk-go@v1.49.16/aws/request/request.go:332 +0x1e imageservice/single-file-download-url/service.GetPresignedURL({0xa9af58, 0xc00002c360}, {0x7eb930, 0x9}, {0x7ed770, 0xd}, 0x3c) /home/gullu/tsproject/imageService/single-file-download-url/service/service.go:88 +0x133 imageservice/single-file-download-url/service.TestGetPresignedURL(0x0?) /home/gullu/tsproject/imageService/single-file-download-url/service/service_test.go:103 +0x20a testing.tRunner(0xc000143040, 0x82bee0) /usr/local/go/src/testing/testing.go:1446 +0x10b created by testing.(*T).Run /usr/local/go/src/testing/testing.go:1493 +0x35f FAIL imageservice/single-file-download-url/service 0.009s
错误原因
原测试中返回的&request.Request{}是空结构体,内部核心字段(如Config、Operation)均为nil。调用Presign方法时,AWS SDK内部会访问这些未初始化的字段,导致空指针异常。此外,原测试用指针直接匹配GetObjectInput,但函数内部会创建新的指针实例,导致匹配失败。
修复后的单元测试代码
package service import ( "testing" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3iface" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) type MockS3API struct { mock.Mock s3iface.S3API } func (m *MockS3API) GetObjectRequest(input *s3.GetObjectInput) (*request.Request, *s3.GetObjectOutput) { args := m.Called(input) return args.Get(0).(*request.Request), args.Get(1).(*s3.GetObjectOutput) } // 测试预签名URL生成成功场景 func TestGetPresignedURL_Success(t *testing.T) { mockSvc := &MockS3API{} bucketName := "my-bucket" objectKey := "my-object-key" expirationTime := 60 // 创建初始化必要字段的Request实例,避免空指针 mockReq := &request.Request{ Config: &aws.Config{ Region: aws.String("us-east-1"), SignatureVersion: aws.String("v4"), }, Operation: &request.Operation{ Name: "GetObject", HTTPMethod: "GET", HTTPPath: "/{Bucket}/{Key}", }, Params: &s3.GetObjectInput{ Bucket: aws.String(bucketName), Key: aws.String(objectKey), }, } // 匹配输入参数的内容而非指针 mockSvc.On("GetObjectRequest", mock.MatchedBy(func(input *s3.GetObjectInput) bool { return aws.StringValue(input.Bucket) == bucketName && aws.StringValue(input.Key) == objectKey })).Return(mockReq, &s3.GetObjectOutput{}) url, err := GetPresignedURL(mockSvc, bucketName, objectKey, expirationTime) assert.NoError(t, err) assert.NotEmpty(t, url) mockSvc.AssertExpectations(t) } // 测试预签名URL生成失败场景 func TestGetPresignedURL_PresignError(t *testing.T) { mockSvc := &MockS3API{} bucketName := "my-bucket" objectKey := "my-object-key" expirationTime := 60 // 创建缺少必要配置的Request实例,触发Presign错误 mockReq := &request.Request{ Config: &aws.Config{}, // 不设置Region,导致签名失败 Operation: &request.Operation{ Name: "GetObject", HTTPMethod: "GET", HTTPPath: "/{Bucket}/{Key}", }, Params: &s3.GetObjectInput{ Bucket: aws.String(bucketName), Key: aws.String(objectKey), }, } mockSvc.On("GetObjectRequest", mock.MatchedBy(func(input *s3.GetObjectInput) bool { return aws.StringValue(input.Bucket) == bucketName && aws.StringValue(input.Key) == objectKey })).Return(mockReq, &s3.GetObjectOutput{}) url, err := GetPresignedURL(mockSvc, bucketName, objectKey, expirationTime) assert.Error(t, err) assert.Empty(t, url) mockSvc.AssertExpectations(t) }
关键修复点
- 初始化Request核心字段:为
request.Request实例填充Config(包含Region和签名版本)、Operation和Params字段,避免SDK内部访问nil字段。 - 参数内容匹配:使用
mock.MatchedBy匹配GetObjectInput的内容,而非直接比较指针,解决函数内部新创建指针导致的匹配失败问题。 - 覆盖错误场景:新增Presign失败的测试用例,验证函数的错误处理逻辑。
内容的提问来源于stack exchange,提问作者gullu
相关产品推荐
相关产品推荐

