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

S3预签名API单元测试求助:空指针异常排查与修复

S3预签名URL生成代码单元测试修复方案

业务代码

type S3Client struct {
    client s3iface.S3API
}

type S3API interface {
    PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error)
    GetObjectRequest(*s3.GetObjectInput) (*request.Request, *s3.GetObjectOutput)
}

type Request interface {
    Presign(expire time.Duration) (string, error)
}

func NewS3Client() S3Client {
    awsConfig := awstools.AWS{Config: &aws.Config{Region: aws.String("us-west-2")}}
    awsSession, _ := awsConfig.Get()
    return S3Client{
        client: s3.New(awsSession),
    }
}

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
}

原有测试代码

type MockRequestImpl struct {
    request.Request
}

func (m *MockRequestImpl) Presign(input time.Duration) (string, error) {
    return preSignFunc(input)
}

type MockS3Client struct {
    s3iface.S3API
}

func init() {
    s = S3Client{
        client: &MockS3Client{},
    }
}

func (m *MockS3Client) GetObjectRequest(input *s3.GetObjectInput) (*request.Request, *s3.GetObjectOutput) {
    return getObjectFunc(input)
}

func TestS3Service_GetPreSignedUrl(t *testing.T) {
    t.Run("should not throw error", func(t *testing.T) {
        getObjectFunc = func(input *s3.GetObjectInput) (*request.Request, *s3.GetObjectOutput) {
            m := MockRequestImpl{}.Request
            return &m, &s3.GetObjectOutput{}
        }
        preSignFunc = func(expire time.Duration) (string, error) {
            return "preSigned", nil
        }

        url, err := s.GetPreSignedUrl("bucket", "objectKey")
        assert.Equal(t, "preSigned", url)
        assert.NoError(t, err)
    })
}

运行错误信息

=== RUN   TestS3Service_GetPreSignedUrl
=== RUN   TestS3Service_GetPreSignedUrl/should_not_throw_error
--- FAIL: TestS3Service_GetPreSignedUrl (0.00s)
    --- FAIL: TestS3Service_GetPreSignedUrl/should_not_throw_error (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=0x2 addr=0x0 pc=0x102ca1eb4]

问题分析及修复

问题根源

  1. 命名冲突:自定义的Request接口与AWS SDK的request包重名,导致代码混淆。
  2. 空指针问题:MockRequestImpl嵌入的request.Request是接口类型,初始化时默认值为nil,测试中返回的&m实际是指向nil接口的指针,调用Presign时触发空指针panic。
  3. 不必要的接口指针:S3API接口中GetObjectRequest返回*request.Request(接口指针),Go中接口本身可直接存储实现类的指针/值,返回接口指针完全没必要,反而增加Mock难度。

修复步骤

1. 优化业务代码(推荐)

先解决命名冲突和接口指针问题,让代码更易测试:

// 重命名自定义接口,避免与SDK包冲突
type S3PresignRequest interface {
    Presign(expire time.Duration) (string, error)
}

// 调整S3API接口,返回接口而非接口指针
type S3API interface {
    PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error)
    GetObjectRequest(*s3.GetObjectInput) (S3PresignRequest, *s3.GetObjectOutput)
}

// 修改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. 修正测试代码

针对优化后的业务代码,编写可正常运行的测试:

import (
    "testing"
    "time"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/service/s3"
    "github.com/aws/aws-sdk-go/service/s3/s3iface"
    "github.com/stretchr/testify/assert"
)

var (
    s             S3Client
    preSignResult string
    preSignErr    error
)

// 自定义Mock请求实现,实现S3PresignRequest接口
type MockS3PresignRequest struct{}

func (m *MockS3PresignRequest) Presign(expire time.Duration) (string, error) {
    return preSignResult, preSignErr
}

// Mock S3客户端
type MockS3Client struct {
    s3iface.S3API
}

func (m *MockS3Client) GetObjectRequest(input *s3.GetObjectInput) (S3PresignRequest, *s3.GetObjectOutput) {
    return &MockS3PresignRequest{}, &s3.GetObjectOutput{}
}

func init() {
    s = S3Client{
        client: &MockS3Client{},
    }
}

func TestS3Service_GetPreSignedUrl(t *testing.T) {
    t.Run("should return valid pre-signed url without error", func(t *testing.T) {
        // 设置预期返回值
        preSignResult = "preSigned"
        preSignErr = nil

        url, err := s.GetPreSignedUrl("bucket", "objectKey")
        assert.Equal(t, "preSigned", url)
        assert.NoError(t, err)
    })

    t.Run("should return error when presign fails", func(t *testing.T) {
        preSignResult = ""
        preSignErr = assert.AnError

        url, err := s.GetPreSignedUrl("bucket", "objectKey")
        assert.Empty(t, url)
        assert.ErrorIs(t, err, assert.AnError)
    })
}

兼容原有业务代码的修复(不修改业务代码)

如果无法修改业务代码,可针对原代码的问题调整测试:

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"
)

var (
    s             S3Client
    preSignFunc   func(time.Duration) (string, error)
)

// 实现SDK request.Request的Presign方法,空实现其他必要方法
type MockRequest struct{}

func (m *MockRequest) Presign(expire time.Duration) (string, error) {
    return preSignFunc(expire)
}

// 空实现SDK request.Request的其他方法,避免编译错误
func (m *MockRequest) Send() error { return nil }
func (m *MockRequest) Sign() error { return nil }
func (m *MockRequest) Build() error { return nil }
func (m *MockRequest) Error() error { return nil }
// 根据SDK版本补充其他必要方法

type MockS3Client struct {
    s3iface.S3API
}

func (m *MockS3Client) GetObjectRequest(input *s3.GetObjectInput) (*request.Request, *s3.GetObjectOutput) {
    mockReq := &MockRequest{}
    // 类型断言为SDK的request.Request接口
    return mockReq.(*request.Request), &s3.GetObjectOutput{}
}

func init() {
    s = S3Client{
        client: &MockS3Client{},
    }
}

func TestS3Service_GetPreSignedUrl(t *testing.T) {
    t.Run("should not throw error", func(t *testing.T) {
        preSignFunc = func(expire time.Duration) (string, error) {
            return "preSigned", nil
        }

        url, err := s.GetPreSignedUrl("bucket", "objectKey")
        assert.Equal(t, "preSigned", url)
        assert.NoError(t, err)
    })
}

内容的提问来源于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 06:14:56