Mock AWS services in Golang
Around 9 months ago I started making the transition from Python to Golang, mainly due to the technology stack used in my company.
One difficult spot I found was mocking AWS services.
In this article I am going to demonstrate how to mock AWS services using Golang.
Motivation (Why do we even need to mock)?
In the previous article, I demonstrated how to build, deploy and test a Lambda with Python and Pytest.
During an application development life cycle we want to make sure that the code we write is working properly, and that we’re not breaking other components while adding new ones.
This sounds trivial when working with objects, files, etc: we can mock a file being downloaded and/or opened from the disk, but what happens when we want to test an AWS service?
1. Do we really want to create a designated “test” resource every time we run the test? leave it running? how do we provision it?
2. What happens if it’s an RDS Database that takes 20+ minutes to initialize?
3. What about the cost? things can get really expensive.
This is when we understand that mocking the services we work with is the most convenient and effective way of achieving high code coverage and certainty.
In this example, we’ll use DynamoDB for storing key-value data.
Let’s create a DynamoDB table with an example item:
In Python, I used the great Moto library, in Golang on the other hand, the implementation looks entirely different, and may even seem simpler to some, especially after experimenting with interfaces, which is a core concept in Go.
Instead of using a 3rd party library and a function decorator on our test function, we’re going to use an interface for mocking AWS Services, and AWS provides us with a mock package for each service.
Instead of passing the DynamoDB client directly to the function, we use the Config struct, with a DynamoDB field that can be overridden.
Why didn’t we pass the client directly?
This is very important, because the getItem function expects a certain type, but the “real” DynamoDB client is of type dynamodb.DynamoDB, and the mock client is of type main.DynamodbMockClient (see below).
To work around this, we use the Config struct that contains the DynamoDB client, and we can make it use both the real/mock implementation while keeping our code clean and even agnostic to that.
As you can see, in the test file we’re overriding the config’s DynamoDB client (created with the AWS session when initialized) with DynamodbMockClient, that is the mock library.
We also have to override the DynamodbMockClient’s GetItem method so it uses the mock client and generate mock data for us.
Before running and testing, we need to install our dependencies by running:
go get -d .
Now we’re ready to run.
Without 3rd party libraries, minimal code changes and overall clean and elegant code, we’re able to mock DynamoDB.
Unsurprisingly, the implementation for other services follows the same principles, so once you mock a service, you basically know how to mock them all.
Once you learn Go’s core principles, it becomes easier and more intuitive as you progress.
I hope you find it useful.