AWS API client mocking with Go
While learning how to mock the AWS API client for unit testing in Go, I discovered the majority of resources are targeted at the Go veteran. The aim of this article is to bridge the gaps left and explain in practical terms how to get started.
The Setup
For the purposes of this blog, we have a single task: return a list of CIDR blocks associated with an AWS VPC. A simple pattern would be:
main()
calls ListVpcCidrBlocks()
. A new AWS session is created, and then used to create the concrete EC2 client, which makes the DescribeVpcs
call. This is great, except it makes unit testing difficult as the only way to test it is against a real live VPC running in AWS.
The Dependency Injection
Dependency Injection is a topic all its own, but for our purposes it can be defined as a method to make code more modular, which in turn makes it easier to test. Instead of bundling the creation of the EC2 client inside of ListVpcCidrBlocks()
we are now creating the client outside of the function and injecting it as an input parameter:
Functionally, nothing has changed. Both of the above examples return the exact same result.
The Service Client Interface
AWS has an official blog talking about using API interfaces for testing, but the gist is that instead of using a concrete service client (What you get from a call to ec2.New(sess)
) you can instead use the service interface:
We’re now importing the github.com/aws/aws-sdk-go/service/ec2/ec2iface
package and the ListVpcCidrBlocks
function signature has changed from taking a pointer to the EC2 client (*ec2.EC2
) to taking the EC2API interface. Functionally nothing has changed from the first example, we’re still returning a list of CIDR blocks attached to the VPC, but now from a testing perspective we have a way in.
The Test
Because we now are using the service interface instead of a service client, we are able to “intercept” function calls and return our own data instead of making the API call to AWS. A look at the full test to give context:
First, the mocked EC2 interface is created along with the methods that we want to “intercept” as fields in the mockedEC2
struct:
ec2iface.EC2API
here is an embedded anonymous struct and provides access to the entire service interface through the mock. We have only a single method, DescribeVpcs
, as that is the only API call made in ListVpcCidrBlocks()
.
When the DescribeVpcs
call is made through the mock, it will hit this method, and then be returned whatever value is stored in the DescribeVpcsOutput
field.
Next, the static data that we are going to return from the mock must be defined:
Finally, the key here is that our mockedEC2
struct should be used in place of the concrete EC2 client:
This is the mechanism by which the API call is routed to our mock and not to AWS. Since the method of the mock returns a pointer to the DescribeVpcsOutput
field, we can change the output returned based on the test case.
❯ go test -v
=== RUN TestListVpcCidrBlocks
=== RUN TestListVpcCidrBlocks/SingleCidrAssociation
=== RUN TestListVpcCidrBlocks/MultipleCidrAssociation
--- PASS: TestListVpcCidrBlocks (0.00s)
--- PASS: TestListVpcCidrBlocks/SingleCidrAssociation (0.00s)
--- PASS: TestListVpcCidrBlocks/MultipleCidrAssociation (0.00s)
PASS
ok github.com/brandonstrohmeyer/aws-mock-go 0.234s
The full example code can be found on github.