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

能否不修改参数对调用AWS Secrets Manager的Go函数做单元测试?

Question

I'm new to Go and wrote a getAWSSecrets function that fetches secrets from AWS Secrets Manager:

// Helper function to get secret from AWS Secret Manager
func getAWSSecrets() (secretMap map[string]string, err error) {
    // Create new AWS session in order to get db info from SecretsManager
    sess, err := session.NewSession()
    if err != nil {
        return nil, err
    }
    // Create a new instance of the SecretsManager client with session
    svc := secretsmanager.New(sess)
    // Get secret config values
    req, resp := svc.GetSecretValueRequest(&secretsmanager.GetSecretValueInput{
        SecretId: aws.String("my/secret/string"),
    })
    err = req.Send()
    if err != nil {
        return nil, err
    }
    ...
}

I need to write unit tests for this function and know that AWS provides a Secrets Manager Interface for unit testing, whose examples pass the Secrets Manager client into the function under test to inject a mock service. I'm wondering: is this the only way to unit test this function? Or can I directly mock the Secrets Manager service inside the current function?


Answer

Great question! You actually have two valid approaches here—neither is the "only" way, but they come with different tradeoffs depending on your long-term code goals.

This is the approach AWS highlights in their examples, and it’s the cleanest, most maintainable solution. The core idea is to decouple your function from the concrete Secrets Manager client by accepting an interface instead of creating the client inside the function.

Here's how to adjust your code:

  • Use the secretsmanageriface.SecretsManagerAPI interface (from the AWS SDK) as a parameter. This interface defines all the methods your function uses (like GetSecretValueRequest), so any mock implementing it will work seamlessly.
import (
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/secretsmanager"
    "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
)

// Helper function to get secret from AWS Secret Manager
func getAWSSecrets(svc secretsmanageriface.SecretsManagerAPI) (secretMap map[string]string, err error) {
    // No need to create session/client inside the function anymore
    req, resp := svc.GetSecretValueRequest(&secretsmanager.GetSecretValueInput{
        SecretId: aws.String("my/secret/string"),
    })
    err = req.Send()
    if err != nil {
        return nil, err
    }
    ...
}

// For production use, call it with a real client:
func main() {
    sess, err := session.NewSession()
    if err != nil {
        // handle error
    }
    svc := secretsmanager.New(sess)
    secrets, err := getAWSSecrets(svc)
    // use secrets
}

For testing, you can use a mock implementation of SecretsManagerAPI (either write your own or use libraries like testify/mock or gomock). This lets you control exactly what the client returns without touching real AWS services.

2. Mocking Client Initialization Directly

If you really don’t want to modify the function signature right now, you can mock the session and client creation logic by turning those calls into package-level variables. This is a bit hacky but works for quick tests.

Here's how to adjust your code:

// Define package-level variables for initialization functions
var (
    newSession = session.NewSession
    newSecretsManager = secretsmanager.New
)

// Helper function to get secret from AWS Secret Manager
func getAWSSecrets() (secretMap map[string]string, err error) {
    // Use package-level variables instead of direct function calls
    sess, err := newSession()
    if err != nil {
        return nil, err
    }
    svc := newSecretsManager(sess)
    req, resp := svc.GetSecretValueRequest(&secretsmanager.GetSecretValueInput{
        SecretId: aws.String("my/secret/string"),
    })
    err = req.Send()
    if err != nil {
        return nil, err
    }
    ...
}

Then in your test file, replace these variables with mock implementations before running the test:

import (
    "testing"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/service/secretsmanager"
    "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
    "github.com/stretchr/testify/mock"
)

// MockSecretsManager implements SecretsManagerAPI for testing
type MockSecretsManager struct {
    secretsmanageriface.SecretsManagerAPI
    mock.Mock
}

func (m *MockSecretsManager) GetSecretValueRequest(input *secretsmanager.GetSecretValueInput) (*secretsmanager.GetSecretValueRequest, *secretsmanager.GetSecretValueOutput) {
    args := m.Called(input)
    return args.Get(0).(*secretsmanager.GetSecretValueRequest), args.Get(1).(*secretsmanager.GetSecretValueOutput)
}

func TestGetAWSSecrets(t *testing.T) {
    // Save original variables to restore after test
    originalNewSession := newSession
    originalNewSecretsManager := newSecretsManager
    defer func() {
        newSession = originalNewSession
        newSecretsManager = originalNewSecretsManager
    }()

    // Set up mock client and response
    mockSvc := new(MockSecretsManager)
    mockResp := &secretsmanager.GetSecretValueOutput{
        SecretString: aws.String(`{"username":"test","password":"test123"}`),
    }
    mockSvc.On("GetSecretValueRequest", mock.Anything).Return(&secretsmanager.GetSecretValueRequest{}, mockResp)

    // Replace package-level variables with mocks
    newSession = func(optFns ...session.Option) (*session.Session, error) {
        return &session.Session{}, nil
    }
    newSecretsManager = func(sess *session.Session, optFns ...aws.Option) *secretsmanager.SecretsManager {
        return mockSvc.(*secretsmanager.SecretsManager)
    }

    // Run test and assert results
    secretMap, err := getAWSSecrets()
    if err != nil {
        t.Fatalf("expected no error, got %v", err)
    }
    if secretMap["username"] != "test" {
        t.Errorf("expected username 'test', got '%s'", secretMap["username"])
    }
    mockSvc.AssertExpectations(t)
}

Which Should You Choose?

  • Dependency Injection is the better long-term choice. It makes your code more modular, easier to test, and simpler to adapt if you ever need to switch to a different secrets store.
  • Direct Mocking is a quick fix if you can’t modify the function signature right now, but it’s less clean and can lead to brittle tests if your initialization logic changes.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:29:19