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

如何为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)
}

关键修复点

  1. 初始化Request核心字段:为request.Request实例填充Config(包含Region和签名版本)、Operation和Params字段,避免SDK内部访问nil字段。
  2. 参数内容匹配:使用mock.MatchedBy匹配GetObjectInput的内容,而非直接比较指针,解决函数内部新创建指针导致的匹配失败问题。
  3. 覆盖错误场景:新增Presign失败的测试用例,验证函数的错误处理逻辑。

内容的提问来源于stack exchange,提问作者gullu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 10:58:12