Mocking the AWS SDK With Go

Mocking a client library is a common technique when building test-driven development. In golang, this can be done by creating structs that implement interfaces and then override the methods you are trying to mock. This example of mocking can be done with any method, but for this post, I will use AWS Organizations to demonstrate.

Implementation Code

First, we want to create a struct that will be used as the methods' instance. As you see below, we are going to implement the interface of the SDK provided organizations API:

type Organizations struct {
	Client organizationsiface.OrganizationsAPI
}

Then once we have defined that, we will have to instantiate the method we want to implement, which for the main implementation will be a pass-through for the method used by the client.

func (s *Organizations) ListAccounts(in *organizations.ListAccountsInput) (*organizations.ListAccountsOutput, error) {
	result, err := s.Client.ListAccounts(in)
	return result, err
}

Finally, we will want to create a method we will test. We must parameterize the struct to help our test define which method to use.

func WhatAreMyAccounts(client *Organizations) (*organizations.ListAccountsOutput, error) {
	return client.ListAccounts(
		&organizations.ListAccountsInput{
			MaxResults: aws.Int64(5),
			NextToken:  nil,
		},
	)
}

Test Code

For testing, we will create our Mock struct, followed by our method override, and then wrap that within a given test. The MockOrganization struct will implement an interface, which we can later utilize as the organization's client.

type MockedOrganizations struct {
	organizationsiface.OrganizationsAPI
}

For the test, we want to guarantee a response. Therefore, we will define a separate implementation of the organization interface, of which we will return a constant value for the test suite.

func (m *MockedOrganizations) ListAccounts(in *organizations.ListAccountsInput) (*organizations.ListAccountsOutput, error) {
	return &organizations.ListAccountsOutput{
		Accounts: []*organizations.Account{
			{
				Arn:   aws.String(""),
				Email: aws.String("[email protected]"),
				Id:    aws.String("234567890"),
				Name:  aws.String("test-1"),
			},
			{
				Arn:   aws.String(""),
				Email: aws.String("[email protected]"),
				Id:    aws.String("123456789"),
				Name:  aws.String("test-2"),
			},
		},
	}, nil
}

On the test code, by using our mocked interface, we can create an Organizations object. The application's function then calls our mocked response to yield the result to test.

func TestListAccounts(t *testing.T) {
	test := Organizations{
		Client: &MockedOrganizations{},
	}
	resp, err := WhatAreMyAccounts(&test)
	assert.Equal(t, len(resp.Accounts), 2)
	assert.NoError(t, err)
}

Results

➜  main go test -v
=== RUN   TestListAccounts
--- PASS: TestListAccounts (0.00s)
PASS
ok      main/cmd/main   0.117s

To see the full code, check out the below gists:

package main
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/organizations"
"github.com/aws/aws-sdk-go/service/organizations/organizationsiface"
)
type Organizations struct {
Client organizationsiface.OrganizationsAPI
}
func (s *Organizations) ListAccounts(in *organizations.ListAccountsInput) (*organizations.ListAccountsOutput, error) {
result, err := s.Client.ListAccounts(in)
return result, err
}
func WhatAreMyAccounts(client *Organizations) (*organizations.ListAccountsOutput, error) {
return client.ListAccounts(
&organizations.ListAccountsInput{
MaxResults: aws.Int64(5),
NextToken: nil,
},
)
}
view raw main.go hosted with ❤ by GitHub
package main
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/organizations"
"github.com/aws/aws-sdk-go/service/organizations/organizationsiface"
"github.com/stretchr/testify/assert"
)
type MockedOrganizations struct {
organizationsiface.OrganizationsAPI
}
func (m *MockedOrganizations) ListAccounts(in *organizations.ListAccountsInput) (*organizations.ListAccountsOutput, error) {
return &organizations.ListAccountsOutput{
Accounts: []*organizations.Account{
{
Arn: aws.String(""),
Email: aws.String("[email protected]"),
Id: aws.String("234567890"),
Name: aws.String("test-1"),
},
{
Arn: aws.String(""),
Email: aws.String("[email protected]"),
Id: aws.String("123456789"),
Name: aws.String("test-2"),
},
},
}, nil
}
func TestListAccounts(t *testing.T) {
test := Organizations{
Client: &MockedOrganizations{},
}
resp, err := WhatAreMyAccounts(&test)
assert.Equal(t, len(resp.Accounts), 2)
assert.NoError(t, err)
}
view raw main_test.go hosted with ❤ by GitHub