Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang Mocking with Elastic

I've built a quick and easy API in Go that queries ElasticSearch. Now that I know it can be done, I want to do it correctly by adding tests. I've abstracted some of my code so that it can be unit-testable, but I've been having some issues mocking the elastic library, and as such I figured it would be best if I tried a simple case to mock just that.

import (
    "encoding/json"
    "github.com/olivere/elastic"
    "net/http"
)
...
func CheckBucketExists(name string, client *elastic.Client) bool {
    exists, err := client.IndexExists(name).Do()
    if err != nil {
        panic(err)
    }
    return exists
}

And now the test...

  import (
      "fmt"
      "github.com/stretchr/testify/assert"
      "github.com/stretchr/testify/mock"
      "testing"
  )

type MockClient struct {
      mock.Mock
  }

  func (m *MockClient) IndexExists(name string) (bool, error) {
      args := m.Mock.Called()
      fmt.Println("This is a thing")
      return args.Bool(0), args.Error(1)
  }

  func TestMockBucketExists(t *testing.T) {
      m := MockClient{}
      m.On("IndexExists", "thisuri").Return(true)

>>    r := CheckBucketExists("thisuri", m)
      assert := assert.New(t)
      assert.True(r, true)
  }

To which I'm yielded with the following error: cannot use m (type MockClient) as type *elastic.Client in argument to CheckBucketExists.

I'm assuming this is something fundamental with my use of the elastic.client type, but I'm still too much of a noob.

like image 650
user2402831 Avatar asked Nov 01 '22 10:11

user2402831


1 Answers

This is an old question, but couldn't find the solution either. Unfortunately, this library is implemented using a struct, that makes mocking it not trivial at all, so the options I found are:

(1) Wrap all the elastic.SearchResult Methods on an interface on your own and "proxy" the call, so you end up with something like:

type ObjectsearchESClient interface {
    // ... all methods... 
    Do(context.Context) (*elastic.SearchResult, error)
}


// NewObjectsearchESClient returns a new implementation of ObjectsearchESClient
func NewObjectsearchESClient(cluster *config.ESCluster) (ObjectsearchESClient, error) {
    esClient, err := newESClient(cluster)
    if err != nil {
        return nil, err
    }

    newClient := objectsearchESClient{
        Client: esClient,
    }
    return &newClient, nil
}
// ... all methods... 
func (oc *objectsearchESClient) Do(ctx context.Context) (*elastic.SearchResult, error) {
    return oc.searchService.Do(ctx)
}

And then mock this interface and responses as you would with other modules of your app.

(2) Another option is like pointed in this blog post that is mock the response from the Rest calls using httptest.Server

for this, I mocked the handler, that consist of mocking the response from the "HTTP call"

func mockHandler () http.HandlerFunc{
    return func(w http.ResponseWriter, r *http.Request) {
        resp := `{
            "took": 73,
            "timed_out": false,
            ... json ... 
            "hits": [... ]
            ...json ... ,
            "aggregations": { ... }
        }`

        w.Write([]byte(resp))
    }
}

Then you create a dummy elastic.Client struct

func mockClient(url string) (*elastic.Client, error) {
    client, err := elastic.NewSimpleClient(elastic.SetURL(url))
    if err != nil {
        return nil, err
    }
    return client, nil
}

In this case, I've a library that builds my elastic.SearchService and returns it, so I use the HTTP like:

    ... 
    ts := httptest.NewServer(mockHandler())
    defer ts.Close()
    esClient, err := mockClient(ts.URL)
    ss := elastic.NewSearchService(esClient)
    mockLibESClient := es_mock.NewMockSearcherClient(mockCtrl)
    mockLibESClient.EXPECT().GetEmployeeSearchServices(ctx).Return(ss, nil)

where mockLibESClient is the library I mentioned, and we stub the mockLibESClient.GetEmployeeSearchServices method making it return the SearchService with that will return the expected payload.

Note: for creating the mock mockLibESClient I used https://github.com/golang/mock

I found this to be convoluted, but "Wrapping" the elastic.Client was in my point of view more work.

Question: I tried to mock it by using https://github.com/vburenin/ifacemaker to create an interface, and then mock that interface with https://github.com/golang/mock and kind of use it, but I kept getting compatibility errors when trying to return an interface instead of a struct, I'm not a Go expect at all so probably I needed to understand the typecasting a little better to be able to solve it like that. So if any of you know how to do it with that please let me know.

like image 177
cesaregb Avatar answered Nov 10 '22 01:11

cesaregb