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

如何为Go语言S3 JSON上传的会话异常与上传失败编写单元测试?

解决Go语言S3上传函数的单元测试覆盖率问题

问题背景

我编写了用于将JSON数据上传至AWS S3的Go函数SaveJSONToS3,代码如下:

func SaveJSONToS3(data map[string]interface{}) error {
    name := fmt.Sprintf("audit_event_-%v.json", time.Now().Format(time.RFC822))
    // Create an S3 session
    sess, err := session.NewSession(&aws.Config{
        Region: aws.String(utils.AWS_REGION), // AWS region
    })
    if err != nil {
        log.Printf(utils.LOG_FAILED_S3_SESSION, err)
    }

    // Create an S3 client
    svc := s3.New(sess)

    // Convert the JSON data to a byte array
    jsonData, err := json.Marshal(data)
    if err != nil {
        log.Printf(utils.LOG_FAILED_MARSHAL_JSON, err)
        return fmt.Errorf("failed to marshal JSON data: %v", err)
    }

    // Set the S3 bucket and object key
    bucket := utils.AWS_S3_BUCKET_NAME
    key := name

    // Upload the JSON data to S3
    _, err = svc.PutObject(&s3.PutObjectInput{
        Body:   aws.ReadSeekCloser(bytes.NewReader(jsonData)),
        Bucket: aws.String(bucket),
        Key:    aws.String(key),
    })
    if err != nil {
        return fmt.Errorf("failed to upload JSON to S3: %v", err)
    }

    return nil
}

目前已完成JSON序列化失败场景的单元测试,但缺少S3会话创建失败和S3上传失败两个分支的测试覆盖。由于会话是在函数内部创建的,无法直接Mock并传入原函数,需要解决这个问题。

解决方案

核心思路是通过依赖注入重构函数,将S3客户端的创建逻辑与核心上传逻辑解耦,让测试时可以传入Mock客户端。

步骤1:重构原函数

将原函数拆分为对外公共函数和内部核心逻辑函数,同时修复原函数中会话创建失败未返回错误的潜在panic问题:

import (
    "bytes"
    "fmt"
    "log"
    "time"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/s3"
)

// SaveJSONToS3 对外暴露的函数,保持原有调用方式
func SaveJSONToS3(data map[string]interface{}) error {
    sess, err := session.NewSession(&aws.Config{
        Region: aws.String(utils.AWS_REGION),
    })
    if err != nil {
        log.Printf(utils.LOG_FAILED_S3_SESSION, err)
        return fmt.Errorf("failed to create S3 session: %v", err)
    }
    svc := s3.New(sess)
    return saveJSONToS3WithClient(data, svc)
}

// saveJSONToS3WithClient 核心上传逻辑,接受外部传入的S3客户端,方便测试
func saveJSONToS3WithClient(data map[string]interface{}, svc *s3.S3) error {
    name := fmt.Sprintf("audit_event_-%v.json", time.Now().Format(time.RFC822))
    
    jsonData, err := json.Marshal(data)
    if err != nil {
        log.Printf(utils.LOG_FAILED_MARSHAL_JSON, err)
        return fmt.Errorf("failed to marshal JSON data: %v", err)
    }

    bucket := utils.AWS_S3_BUCKET_NAME
    key := name

    _, err = svc.PutObject(&s3.PutObjectInput{
        Body:   aws.ReadSeekCloser(bytes.NewReader(jsonData)),
        Bucket: aws.String(bucket),
        Key:    aws.String(key),
    })
    if err != nil {
        return fmt.Errorf("failed to upload JSON to S3: %v", err)
    }

    return nil
}

步骤2:编写缺失的单元测试

使用AWS官方Mock库github.com/aws/aws-sdk-go/aws/mock和猴子补丁库github.com/bouk/monkey来实现测试:

测试S3会话创建失败场景

import (
    "fmt"
    "testing"

    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/bouk/monkey"
)

func TestSaveJSONToS3_SessionCreationError(t *testing.T) {
    // 猴子补丁替换session.NewSession,模拟会话创建失败
    patch := monkey.Patch(session.NewSession, func(cfg *aws.Config) (*session.Session, error) {
        return nil, fmt.Errorf("mock session creation error")
    })
    defer patch.Unpatch()

    err := SaveJSONToS3(map[string]interface{}{"key": "value"})
    expectedErr := "failed to create S3 session: mock session creation error"
    if err == nil || err.Error() != expectedErr {
        t.Errorf("expected error: %s, got: %v", expectedErr, err)
    }
}

测试S3上传失败场景

import (
    "fmt"
    "testing"
    "time"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/mock"
    "github.com/aws/aws-sdk-go/service/s3"
    "github.com/bouk/monkey"
)

func TestSaveJSONToS3_PutObjectError(t *testing.T) {
    // 固定时间,避免动态生成的key导致Mock匹配失败
    fixedTime := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
    timePatch := monkey.Patch(time.Now, func() time.Time {
        return fixedTime
    })
    defer timePatch.Unpatch()

    expectedKey := fmt.Sprintf("audit_event_-%v.json", fixedTime.Format(time.RFC822))
    mockSvc := mock.NewMockS3()

    // 设置Mock的PutObject方法返回错误
    mockSvc.On("PutObject", &s3.PutObjectInput{
        Bucket: aws.String(utils.AWS_S3_BUCKET_NAME),
        Key:    aws.String(expectedKey),
        Body:   aws.ReadSeekCloser(mock.Anything),
    }).Return(nil, fmt.Errorf("mock upload error"))

    // 调用核心逻辑函数
    err := saveJSONToS3WithClient(map[string]interface{}{"key": "value"}, mockSvc)
    expectedErr := "failed to upload JSON to S3: mock upload error"
    if err == nil || err.Error() != expectedErr {
        t.Errorf("expected error: %s, got: %v", expectedErr, err)
    }

    // 验证Mock方法被正确调用
    mockSvc.AssertExpectations(t)
}

依赖安装

执行以下命令安装所需测试依赖:

go get github.com/aws/aws-sdk-go/aws/mock
go get github.com/bouk/monkey

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 21:44:52