Amrit
Amrit

Reputation: 2102

How do I write unit test aws-sdk-go-v2 dynamodb implementation

I am still grasping go-interfaces and I can mock the WaitUntilTableExists func. But unable to mock PutItemRequest.

Here's my main.go snippet

func MyPutItem(d mydata, client dynamodbiface.DynamoDBAPI) error {
    input := &dynamodb.PutItemInput{
        ....
    }
    req := client.PutItemRequest(input)
    result, err := req.Send()
    log.Println(result)
    return err
}

main_test.go snippet

type mockDynamoDBClient struct {
    dynamodbiface.DynamoDBAPI
}

func (m *mockDynamoDBClient) PutItemRequest(input *dynamodb.PutItemInput) dynamodb.PutItemRequest {
    // Most probably this is where I need your help
}

func TestStoreInDynamoDB(t *testing.T) {
    var mockClient = new(mockDynamoDBClient)
    d := mydata{}
    result := DynampDBPutItem(d, mockClient)
    t.Log(result)
}

Upvotes: 3

Views: 12235

Answers (2)

Amrit
Amrit

Reputation: 2102

Faking the SDK like this works:

main_test.go

type fakeDynamoDBClient struct {
    dynamodbiface.DynamoDBAPI
}

func (m *fakeDynamoDBClient) GetItemRequest(input *dynamodb.GetItemInput) dynamodb.GetItemRequest {
    return dynamodb.GetItemRequest{
        Request: &aws.Request{
            Data: &dynamodb.GetItemOutput{
                Item: map[string]dynamodb.AttributeValue{
                    "count": dynamodb.AttributeValue{
                        N: aws.String("10"),
                    },
                },
            },
        },
    }
}

func (m *fakeDynamoDBClient) PutItemRequest(input *dynamodb.PutItemInput) dynamodb.PutItemRequest {
    return dynamodb.PutItemRequest{
        Request: &aws.Request{
            Data: &dynamodb.PutItemOutput{},
        },
    }
}

func TestUpdateCount(t *testing.T) {
    err := UpdateCount(10, &fakeDynamoDBClient{})
    if err != nil {
        t.Error("Failed to update badge count on dynamodb", err)
    }
}

main.go

func UpdateCount(count int, client dynamodbiface.DynamoDBAPI) error {
    ...
}

Upvotes: 2

Zak
Zak

Reputation: 5898

Taking your example, you could do your assertions directly in the mock

type mockDynamoDBClient struct {
    t *testing.T
    expected *dynamodb.PutItemInput
    response *dynamodb.PutItemOutput
    dynamodbiface.DynamoDBAPI
}

func (m *mockDynamoDBClient) PutItemRequest(input *dynamodb.PutItemInput) dynamodb.PutItemOutput {
    // some kind of equality check
    if !reflect.DeepEqual(m.expected, input) {
        t.Errorf(...// some error message)
    }
    return m.response
}

The main problems with this example are:

t *testing.T, expected *dynamodb.PutItemInput and response response *dynamodb.PutItemOutput all need to be inside the struct which feels messy.

Instead you could use an anonymous function to do this:

type mockDynamoDBClient struct {
    f func(input *dynmaodb.PutItemInput) *dynamodb.PutItemOutput
    dynamodbiface.DynamoDBAPI
}

func (m *mockDynamoDBClient) PutItemRequest(input *dynamodb.PutItemInput) dynamodb.PutItemOutput {
    return m.f(input)
}

Now in the test code you can make slightly better use of the mock struct:

m := &mockDynamoDBClient{
    f: func(input *dynamodb.PutItemInput) *dynamodb.PutItemOutput {
        // assertions on input
        // return mock responses
    }
}

EDIT based on comment:

You should also consider making your MyPutItem function dependent on the smallest interface possible. If you only need access to the PutItemRequest method then you can create your own interface for that method and use that in MyPutItem

type MyDynamoPutter interface {
    func (c *DynamoDB) PutItemRequest(input *PutItemInput) PutItemRequest
}

Then in MyPutItem you can use your own interface:

func MyPutItem(d mydata, client MyDynamoPutter) error {
    input := &dynamodb.PutItemInput{
        ....
    }
    req := client.PutItemRequest(input)
    result, err := req.Send()
    log.Println(result)
    return err
}

This reduces the surface area that you need to mock!

Upvotes: 4

Related Questions