How do I mock a s3Client so that I can run GetObject() in a Golang test?

Viewed 3638

I have a function that accepts a path and an S3Client: ListObjects(folder, s3Client) I want it to accept a "real" client in production. I.e: s3.New(session.New() But when I run my tests I'd prefer it to accept my mocked version so that we don't talk to AWS in our tests. My approach to achieve this is to make an interface that matches both the real S3Client and my mocked version.

Is this the best approach or am I missing anything?

First I have defined the interface with the function I plan to mock.

  package core

  import "github.com/aws/aws-sdk-go/service/s3"

  type MyS3ClientInterface interface {
         ListObjects(input *s3.ListObjectsInput) (*s3.ListObjectsOutput, error)
  }

GetObjectsFromS3 is implemented like this:

// GetObjectsFromS3 returns a list of objects found in provided path on S3
func GetObjectsFromS3(path string, s3Client core.MyS3ClientInterface) ([]*core.Asset, error) {
      // build input variable
      result, err := s3Client.ListObjects(input)
      // cut..
}

Here's my mocked version of s3Client.ListObjects

  package test_awstools

  import (
          "github.com/aws/aws-sdk-go/service/s3"
  )

  type MyS3Client struct{}

  func (m MyS3Client) ListObjects(input *s3.ListObjectsInput) (*s3.ListObjectsOutput, error) {
          output := &s3.ListObjectsOutput{}
          return output, nil
  }

  func (m MyS3Client) GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
          // output := &s3.GetObject()
          return nil, nil
  }

internal/awstools/bucket_test.go

  1 package awstools
  2
  3 import (
  4         "fmt"
  5         "testing"
  6
  7         "internal/test_awstools"
  8 )
  9
 10 func TestListObjects(t *testing.T) {
 11         folder := "docs"
            // Use my mocked version of the S3 client.
 12         s3Client := &test_awstools.MyS3Client{}
            // And I pass that along to the ListObject function
 13         objects, err := ListObjects(folder, s3Client)
 14         if err != nil {
 15                 t.Error(err)
 16         }
 17         fmt.Println(objects)
 18 }
1 Answers

One small improvement I could suggest would be to allow your mock struct to accept a function, that way it can vary between tests. Something like this:

  package test_awstools

  import (
          "github.com/aws/aws-sdk-go/service/s3"
  )

  type MyS3Client struct {
    ListObjectsFunc func(*s3.ListObjectsInput) (*s3.ListObjectsOutput, error)
    GetObjectFunc func(*s3.GetObjectInput) (*s3.GetObjectOutput, error)
  }

  func (m *MyS3Client) ListObjects(input *s3.ListObjectsInput) (*s3.ListObjectsOutput, error) {
    return m.ListObjectsFunc(input)
  }

  func (m *MyS3Client) GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
    return m.GetObjectFunc(input)
  }

Now in your test you can set the *Func values on your struct so that your test is much more declarative about the stubbed behavior:

func TestListObjects(t *testing.T) {
  folder := "docs"

  // Use my mocked version of the S3 client.
  s3Client := &test_awstools.MyS3Client{}

  // Set the ListObjectsFunc to behave the way you want
  s3Client.ListObjectsFunc = func(input *s3.ListObjectsInput) (*s3.ListObjectsOutput, error) {
    output := &s3.ListObjectsOutput{}
    return output, nil
  }

  // Make the call to list objects (which is now mocked)
  objects, err := s3Client.ListObjects(folder, s3Client)

  // Do your assertions
  if err != nil {
    t.Error(err)
  }
  fmt.Println(objects)
}
Related