Search code examples
unit-testinggotypesmockingassertions

Have a Go function accept different structs as input with methods


I'm rather new to Go and I can't seem to wrap my head around it's struct/interface system while trying to create a mock object for an AWS s3manager uploader's unit testing.

In my package file I have:

package uploader

import (
        "fmt"
        "github.com/aws/aws-sdk-go/aws"
        "github.com/aws/aws-sdk-go/aws/session"
        "github.com/aws/aws-sdk-go/service/s3/s3manager"
        "os"
)

func GetS3Uploader() *s3manager.Uploader {
    conf := aws.Config{Region: aws.String("eu-west-1")}
    sess := session.New(&conf)
    uploader := s3manager.NewUploader(sess)
    return uploader
}

func uploadFile(uploader interface{}) {

    uploader.Upload(&s3manager.UploadInput{
       Bucket: aws.String("A"),
       Key:    aws.String("B"),
       Body:   bytes.NewReader([]byte("C")),
    })

}

and in the matching uploader_test.go there is the following code, containing the mock objects:

package uploader_test

import (
    . "github.com/onsi/ginkgo"
    . "github.com/something/reponame/uploader"
    "github.com/aws/aws-sdk-go/service/s3/s3manager"
)

type mockUploadOutput struct {
    Location  string
    VersionID *string
    UploadID  string
}

type mockUploader struct {
}

func (*mockUploader) Upload(input *s3manager.UploadInput) (mockUploadOutput, error) {
    versionID := "TESTVERSION"
    mockUploadResponse := mockUploadOutput{
        Location:  "TESTLOCATION",
        VersionID: &versionID,
        UploadID:  "TESTUPLOADID",
    }
    return mockUploadResponse, nil
}

var _ = Describe("Reportuploader", func() {

    var (
        mockUp mockUploader
    )

    Describe("Upload()", func() {
        Context("With mocked uploader object", func() {
            It("should return the predefined mockUploadResponse", func() {

                uploadFile(mockUp)

            })
        })
    })
})

But when I try to run it, I get the following error:

uploader.Upload undefined (type interface {} is interface with no methods)

My aim is to have the uploadFile function accept both the *s3manager.Uploader object and the mocked mockUploader as valid arguments, and recognize both their Upload methods. I tried asserting the type before calling the Upload method, but that only gave a different error. Can anyone help, and tell me what am I doing wrong?


Solution

  • You set the type signature to func uploadFile(uploader interface{}) {, which means it accepts anything as an argument, but you cannot call any method on uploader, because your type signature interface{} does not any methods on it.

    It looks like what you want to do is:

    type Uploader interface {
        Upload(input *s3manager.UploadInput) 
    }
    
    func uploadFile(uploader Uploader) {
        uploader.Upload(&s3manager.UploadInput{
            Bucket: aws.String("A"),
            Key:    aws.String("B"),
            Body:   bytes.NewReader([]byte("C")),
        })
    }
    

    But you are going to need to match the function signature, and it looks like one of them has:

    (mockUploadOutput, error) 
    

    and the other a

    (*s3manager.UploadOutput, error)
    

    so, what I would probably do is just return a real (*s3manager.UploadOutput, error) from your mock:

    func (*mockUploader) Upload(input *s3manager.UploadInput) (*s3manager.UploadOutput, error) {
      versionID := "TESTVERSION"
      mockUploadResponse := &s3manager.UploadOutput{
          Location:  "TESTLOCATION",
          VersionID: &versionID,
          UploadID:  "TESTUPLOADID",
      }
      return mockUploadResponse, nil
    }