You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

S3预签名API单元测试报错:空指针引用问题排查与解决

Go S3预签名URL单元测试修复方案

错误原因分析

测试触发panic: runtime error: invalid memory address or nil pointer dereference的核心原因有两点:

  1. 测试中返回的request.Request是零值结构体,其内部签名相关依赖字段为nil,调用Presign时触发空指针访问。
  2. 自定义的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 01:59:56