Testing code that accesses AWS can seem impossible, but it’s really not that hard! Let’s take a look at how we can use Go’s features to write useful tests for this kind of code, without a whole lot of work.
Good morning! This is a quick talk on how you can test AWS APIs in Go, with very little pain. My name is Steve Scaffidi, and I'm a lead engineer at HERE Technologies.
When you write code that interacts with AWS, you're writing code that interacts with a "live" system. It's always changing, and testing code against live systems can be a challenge.
But you *do* test it, RIGHT? Well, you can and you should.
In the case of using AWS APIs, you can essentially *mock* any part of AWS, and the golang SDK's API makes it possible. So, let's look at some code that uses the AWS API.
It's a simple example in this case, that asks AWS for all the available EC2 instances then returns a list of the instance-IDs.
As usual, you create an AWS session...
...then instantiate the appropriate API client with that session. In this case, it's the EC2 API client.
I use GoLand as my IDE and it has a handy auto-documentation function and as you can see here, the new() function returns an EC2 client object. We can then pass this around and make calls on it, to interact with the EC2 API.
Now, the AWS APIs are rather consistent. You'll see a particular pattern for each method where it takes an "Input" data structure as an argument, and returns an "Output" data structure. For example, the "DescribeInstances" method on the EC2 client follows this pattern, where it takes a "DescribeInstancesInput" object and returns a "DescribeInstancesOutput" object.
This is important to know, because you'll be constructing these objects for your tests later. The API documentation is rather good, and should make figuring it out straight-forward.
Anyhow, we make the call, get the result, and process it to extract the info we need.
Simple.
But how do you test this?
The special sauce? Don't mock the session, which was not made for that kind of abuse. Mock the API clients. This is vastly simpler because the client APIs were designed for it.
Each API is backed by an underlying Go Interface. All you need to do is make the interface the type you pass around instead of the concrete implementation type.
Once your code is refactored to do that, you can very easily write tests.
Here is an example of the same code, refactored to be testable.
In this case, we don't pass the AWS session around, we pass the EC2 client we instantiated with the session.
But the key is that the function we're testing takes not the EC2 client object, but rather an EC2API *interface*.
It's this interface that allows us to mock the EC2 client.
Now, let's look at the test code... First some helper functions to construct the DescribeInstancesOutput object we want to return from our mocked client. Given a list of Instance IDs, it will create the required data structures.
Now here's the mock client, with only the method we want to mock. This function simply ignores the input and constructs the output object, using the helper functions from the previous slide, but you can easily make it much more sophisticated, as my real-world code using this technique is.
Note that we've augmented the mock client with an instanceIds field, which we can populate with whatever we want from our test code, thus customizing what the DescribeInstances method returns.
Now, let's put it all together - here's the actual test, written in a table-driven style.
The trick is highlighted here... We construct the mock client and pass that to the function we're testing, and voila, it returns exactly what we told it to return...
Time for a live demo? Let me open my IDE and prove this all works, and show off the actual code!