S3预签名API单元测试报错:空指针引用问题排查与解决
Go S3预签名URL单元测试修复方案
错误原因分析
测试触发panic: runtime error: invalid memory address or nil pointer dereference的核心原因有两点:
- 测试中返回的
request.Request是零值结构体,其内部签名相关依赖字段为nil,调用Presign时触发空指针访问。 - 自定义的
MockRequestImpl虽重写了Presign方法,但返回的是*request.Request类型而非*MockRequestImpl,实际调用的是AWS SDK原生方法,而非自定义mock逻辑。
优化方案:依赖抽象而非具体实现
通过自定义接口抽象S3客户端和请求对象,解耦业务代码与AWS SDK的具体实现,让mock更简单可靠。
1. 调整业务代码(推荐)
修改自定义的S3API接口,让GetObjectRequest返回自定义的Request接口而非AWS SDK的*request.Request:
type S3Client struct { client S3API // 使用自定义接口而非s3iface.S3API } type S3API interface { PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) GetObjectRequest(*s3.GetObjectInput) (Request, *s3.GetObjectOutput) // 返回自定义Request接口 } type Request interface { Presign(expire time.Duration) (string, error) } // 原NewS3Client方法保持不变,添加适配器适配自定义S3API接口 func NewS3Client() S3Client { awsConfig := awstools.AWS{Config: &aws.Config{Region: aws.String("us-west-2")}} awsSession, _ := awsConfig.Get() return S3Client{ client: &realS3Client{client: s3.New(awsSession)}, } } // 适配器:让AWS原生S3客户端实现自定义S3API接口 type realS3Client struct { client s3iface.S3API } func (r *realS3Client) PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) { return r.client.PutObject(input) } func (r *realS3Client) GetObjectRequest(input *s3.GetObjectInput) (Request, *s3.GetObjectOutput) { req, out := r.client.GetObjectRequest(input) return req, out // AWS的request.Request天然实现自定义Request接口 } // 原GetPreSignedUrl方法无需修改 func (s *S3Client) GetPreSignedUrl(bucket string, objectKey string) (string, error) { req, _ := s.client.GetObjectRequest(&s3.GetObjectInput{ Bucket: aws.String(bucket), Key: aws.String(objectKey), }) urlStr, err := req.Presign(30 * 24 * time.Hour) if err != nil { return "", err } return urlStr, nil }
2. 编写正确的单元测试
基于自定义接口编写mock实现,确保返回的Request实例是有效的mock对象:
import ( "testing" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/s3" "github.com/stretchr/testify/assert" ) var ( s S3Client getObjectFunc func(input *s3.GetObjectInput) (Request, *s3.GetObjectOutput) preSignFunc func(expire time.Duration) (string, error) ) // MockRequestImpl 实现自定义Request接口 type MockRequestImpl struct{} func (m *MockRequestImpl) Presign(expire time.Duration) (string, error) { return preSignFunc(expire) } // MockS3Client 实现自定义S3API接口 type MockS3Client struct{} func (m *MockS3Client) PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) { return nil, nil } func (m *MockS3Client) GetObjectRequest(input *s3.GetObjectInput) (Request, *s3.GetObjectOutput) { return getObjectFunc(input) } func init() { s = S3Client{ client: &MockS3Client{}, } } func TestS3Service_GetPreSignedUrl(t *testing.T) { t.Run("should return valid presigned url without error", func(t *testing.T) { // 设置mock逻辑 getObjectFunc = func(input *s3.GetObjectInput) (Request, *s3.GetObjectOutput) { return &MockRequestImpl{}, &s3.GetObjectOutput{} } preSignFunc = func(expire time.Duration) (string, error) { return "preSignedUrl", nil } // 调用被测方法 url, err := s.GetPreSignedUrl("test-bucket", "test-object-key") // 断言结果 assert.NoError(t, err) assert.Equal(t, "preSignedUrl", url) }) }
关键修复点
- 使用自定义接口抽象依赖,避免直接耦合AWS SDK的具体结构体,让mock更可控。
- Mock对象直接实现自定义接口,确保调用的是mock的
Presign方法而非SDK原生方法。 - 确保返回的
Request实例是有效的非nil对象,避免空指针访问。
内容的提问来源于stack exchange,提问作者rsundhar
相关产品推荐
相关产品推荐

