Background
If your application uses aws-go-sdk-v2, you’ve probably spent some time wondering about the best way to mock all the external calls. Originally, the Go SDK provided something called ifaces
, which were large interfaces defining every single service’s function signatures. This made it straightforward to mock clients, as one could simply use the EC2Iface
interface for internal service calls and then use mockgen
to create mocks for that interface.
With the go-sdk-v2, the developers decided to remove these interfaces, leaving it up to the clients to define their own interfaces with matching function signatures.
Full Example
Let’s take the EC2 service DescribeRegions
as an example. If you look into the AWS SDK code, you will find that no interface is defined for it.
Each service has a package.Client
struct defined (each AWS service has its own package and client).
api_op_DescribeInstances.go
package ec2
type Client struct {
options Options
}
func (c *Client) DescribeInstances(ctx context.Context, params *DescribeInstancesInput, optFns ...func(*Options)) (*DescribeInstancesOutput, error) {
if params == nil {
params = &DescribeInstancesInput{}
}
// Implementation details
}
In Go, if you define an interface with a function matching the signature of DescribeInstances
, then DescribeInstances
can be called using an *ec.Client
struct, and effectively EC2API
will be of type *Client
.
Example:
type EC2API interface {
DescribeInstances(
ctx context.Context,
params *DescribeInstancesInput,
optFns ...func(*Options),
) (*ec2.DescribeInstancesOutput, error)
}
type localEC2 struct {
svc EC2API
}
func NewEC2API(cfg *aws.Config) *localEC2 {
ec2Svc := ec2.NewFromConfig(cfg)
return &localEC2{svc: ec2Svc}
}
Now, you can use localEC2
for your calls, and you can mock DescribeInstances
by implementing EC2API
.
If EC2API
had an extra function, for example NotmatchingFuncSignature()
, then return &localEC2{svc: ec2Svc}
would throw an error, saying:
Cannot use ec2Svc (type *Client) as the type EC2API. Type does not implement EC2API as some methods are missing: NotmatchingFuncSignature() error
This error occurs because nowhere in the package ec2
is there something implementing NotmatchingFuncSignature()
, so &localEC2{svc: ec2Svc}
would fail because the types don’t match.
How to Mock All This?
For mocking, we use mockgen
. We can use Go’s go:generate mockgen
feature like this:
//go:generate mockgen -source=ec2.go -destination=ec2_mock.go -package adapter
type EC2API interface {
DescribeInstances(
ctx context.Context,
params *DescribeInstancesInput,
optFns ...func(*Options),
) (*ec2.DescribeInstancesOutput, error)
}
// ...
If you run this, mockgen will create a new ec2_mock.go
file containing all the required mocks for DescribeInstances
.
A full example would look like this:
type EC2TestSuite struct {
suite.Suite
mockEC2API *adapter.MockEC2API
ec2APICtrl *gomock.Controller
}
func (ts *EC2TestSuite) BeforeTest(suiteName, testName string) {
ts.ec2APICtrl = gomock.NewController(ts.T())
ts.mockEC2API = adapter.NewMockEc2API(ts.ec2APICtrl)
}
func (ts *EC2TestSuite) AfterTest(suiteName, testName string) {
ts.ec2APICtrl.Finish()
}
func TestEC2TestSuite(t *testing.T) {
suite.Run(t, new(EC2TestSuite))
}
func (ts *EC2TestSuite) Test_ShouldDescribeInstances() {
s := NewEC2API()
s.svc = ts.mockEC2API
ts.mockEC2API.EXPECT().DescribeInstances(...).Return(..., nil)
instances, err := s.DescribeInstances(...)
// assertions
}
Voilà! The mock is injected using s.svc = ts.mockEC2API
, and when the actual code hits .DescribeInstances
, your mock will be called.