Blog

Stubbing AWS Service Aufrufe in Golang

Joris Conijn

Joris Conijn

Aktualisiert Oktober 15, 2025
4 Minuten

Ich bin vor kurzem zu Golang als Sprache meiner Wahl gewechselt. (In meinem vorherigen Blog können Sie lesen, warum.) Aber ich bin auch ein großer Fan der testgetriebenen Entwicklung. Mit Python haben Sie einen Stubber, mit dem Sie die AWS-API nachbilden können. Wie machen Sie das also in Golang? In diesem Blog werde ich meine bisherigen Erfahrungen mit Ihnen teilen.

Verwenden Sie die Injektion von Abhängigkeiten

Mein erstes Experiment war die Injektion von Abhängigkeiten. Dazu habe ich den folgenden Code verwendet:

package main

import (
    "context"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/s3"
    "time"
    "log"
    "os"
)

type Request struct {}
type Response struct {}

type Lambda struct {
    s3Client *s3.Client
}

func New() (*Lambda, error) {
    cfg, err := config.LoadDefaultConfig(context.TODO())

    m := new(Lambda)
    m.SetS3Client(s3.NewFromConfig(cfg))
    return m, err
}

func (x *Lambda) SetS3Client(client *s3.Client) {
    x.s3Client = client
}

func (x *Lambda) Handler(ctx context.Context, request Request) (Response, error) {
    // Your lambda code goes here
}

In Ihren Tests können Sie es nun wie folgt verwenden:

package main

import (
    "context"
    "github.com/aws/aws-sdk-go-v2/service/s3"
    "testing"
    "time"
    "log"
    "os"
)

type mockS3Client struct {
    s3.Client
    Error error
}

func (m *mockS3Client) PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
    return &s3.PutObjectOutput{}, nil
}

func TestHandler(t *testing.T) {
    lambda := New()
    lambda.SetS3Client(&mockS3Client{})
    var ctx = context.Background()
    var event Request

    t.Run("Invoke Handler", func(t *testing.T) {
        response, err := lambda.Handler(ctx, event)

        // Perform Assertions
    })
}

Wir injizieren ein gespottetes Objekt, das als Client fungiert, um die API-Aufrufe auszuführen. Mit diesem Ansatz konnte ich nun einige Tests schreiben. Aber mir wurde klar, dass dieser Ansatz ein weiteres Problem mit sich bringt. Was ist zum Beispiel, wenn Sie 2 API-Aufrufe haben, die einen PutObject -Aufruf ausführen. In diesem Beispiel gebe ich ein leeres zurück. Ich möchte aber mehr als ein Szenario testen. Wie steuern Sie also dieses Verhalten in Ihrem Mocking-Objekt?

Verwendung eines Stubbers

Also habe ich weiter recherchiert und das awsdocs/aws-doc-sdk-examples Repository gefunden. Dieses Repository verwendet ein testtools Modul. Also habe ich ein Experiment gestartet, um zu sehen, wie ich dieses Modul verwenden kann. Ich habe den Code wie folgt umstrukturiert:

package main

import (
    "context"
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

type Request struct {}
type Response struct {}

type Lambda struct {
    ctx      context.Context
    s3Client *s3.Client
}

func New(cfg aws.Config) *Lambda {
    m := new(Lambda)
    m.s3Client = s3.NewFromConfig(cfg)
    return m
}

func (x *Lambda) Handler(ctx context.Context, request Request) (Response, error) {
    // Your lambda code goes here
    return Response{}, nil
}

Ich habe der Methode New einen Parameter cfg hinzugefügt, den ich also auch in meiner Hauptmethode übergeben muss.

package main

import (
    "context"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go-v2/config"
    "log"
)

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO())
    if err != nil {
        log.Printf("error: %v", err)
        return
    }
    lambda.Start(New(cfg).Handler)
}

Der Test selbst sieht folgendermaßen aus:

package main

import (
    "bytes"
    "context"
    "encoding/json"
    "errors"
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/service/s3"
    "github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools"
    "io"
    "os"
    "strings"
    "testing"
)

func TestHandler(t *testing.T) {
    var ctx = context.Background()
    var event Request

    t.Run("Upload a file to S3", func(t *testing.T) {
        stubber := testtools.NewStubber()
        lambda := New(*stubber.SdkConfig)

        stubber.Add(testtools.Stub{
            OperationName: "PutObject",
            Input: &s3.PutObjectInput{
                Bucket: aws.String("my-sample-bucket"),
                Key:    aws.String("my/object.json"),
                Body:   bytes.NewReader([]byte{}),
            },
            Output: &s3.PutObjectOutput{},
        })

        response, err := lambda.Handler(ctx, event)
        testtools.ExitTest(stubber, t)

        // Perform Assertions
    })
}

Wie Sie sehen können, haben wir den Mock jetzt in den Test selbst verschoben. Dadurch können Sie die AWS-API auf der Grundlage Ihres Tests reagieren lassen. Der größte Vorteil ist, dass es im Test selbst gekapselt ist. Wenn Sie zum Beispiel ein Szenario hinzufügen möchten, bei dem der Aufruf von PutObject fehlschlägt, fügen Sie Folgendes hinzu:

t.Run("Fail on upload", func(t *testing.T) {
    stubber := testtools.NewStubber()
    lambda := New(*stubber.SdkConfig)
    raiseErr := &testtools.StubError{Err: errors.New("ClientError")}

    stubber.Add(testtools.Stub{
        OperationName: "PutObject",
        Input: &s3.PutObjectInput{
            Bucket: aws.String("my-sample-bucket"),
            Key:    aws.String("my/object.json"),
            Body:   bytes.NewReader([]byte{}),
        },
        Error: raiseErr,
    })

    _, err := lambda.Handler(ctx, event)
    testtools.VerifyError(err, raiseErr, t)
    testtools.ExitTest(stubber, t)
})

Die Definition testtools.VerifyError(err, raiseErr, t) bestätigt, ob der Fehler tatsächlich weitergegeben wird. Die Definition wird den Test nicht bestehen, wenn ein von Ihnen hinzugefügter Stub nicht aufgerufen wurde. Sie können damit überprüfen, ob alle erwarteten API-Aufrufe tatsächlich ausgeführt wurden.

In manchen Fällen möchten Sie bestimmte Felder in Ihrem Input ignorieren. Sie können eine Liste von zu Ihrem Stubber hinzufügen. Dies ist nützlich, wenn Sie keine direkte Kontrolle darüber haben, was gesendet wird.

Fazit

Die testtool ist ein guter Ersatz für den Stubber, den ich in Python verwendet habe. Er ermöglicht es Ihnen, Szenariodaten in Ihrem Test zu kapseln. Damit vermeiden Sie schwer zu pflegende Mock-Objekte. Die arbeitet mit der Konfiguration, so dass Sie nicht jeden Client stubben müssen. Das Ergebnis ist weniger Code, der zum Testen Ihrer Implementierung benötigt wird.

Foto von Klaus Nielsen

Verfasst von

Joris Conijn

Joris is the AWS Practise CTO of the Xebia Cloud service line and has been working with the AWS cloud since 2009 and focussing on building event-driven architectures. While working with the cloud from (almost) the start, he has seen most of the services being launched. Joris strongly believes in automation and infrastructure as code and is open to learning new things and experimenting with them because that is the way to learn and grow.

Contact

Let’s discuss how we can support your journey.