Golang Mocking with Elastic



我在Go中构建了一个快速简单的API,用于查询ElasticSearch。既然我知道这是可以做到的,我想通过添加测试来正确地做到这一点。我已经抽象了一些代码,使其可以进行单元测试,但我在嘲笑弹性库时遇到了一些问题,因此我认为最好尝试一个简单的案例来嘲笑它。

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
}

现在的测试。。。

  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)
  }

对此,我产生了以下错误:cannot use m (type MockClient) as type *elastic.Client in argument to CheckBucketExists

我认为这是我使用elastic.client类型的基本原理,但我仍然是一个傻瓜。

这是一个老问题,但也找不到解决方案。不幸的是,这个库是使用一个结构实现的,这使得嘲笑它一点也不琐碎,所以我发现的选项是:

(1) 将所有的elastic.SearchResult方法封装在您自己的接口上,并"代理"调用,这样您最终会得到这样的结果:

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)
}

然后像对待应用程序的其他模块一样模拟这个界面和响应。

(2) 另一个选项就像这篇博客文章中指出的那样,是使用httptest.Server 模拟Rest调用的响应

为此,我嘲笑了处理程序,它包括嘲笑来自"HTTP调用"的响应

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))
    }
}

然后创建一个虚拟弹性体。客户端结构

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

在这种情况下,我有一个库来构建我的弹性。SearchService并返回它,所以我使用HTTP,比如:

    ... 
    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)

其中mockLibESClient是我提到的库,我们存根mockLibESClient.GetEmployeeSearchServices方法,使其返回SearchService,从而返回预期的有效负载。

注意:为了创建mockLibESClient,我使用了https://github.com/golang/mock

我发现这是令人费解的,但"包裹"的弹性。客户在我看来是更多的工作。

问题:我试着用https://github.com/vburenin/ifacemaker创建一个接口来模拟它,然后用https://github.com/golang/mock模拟那个接口并使用它,但当我试图返回一个接口而不是一个结构时,我一直遇到兼容性错误,我根本不是一个Go期望值,所以我可能需要更好地理解类型转换才能这样解决它。所以,如果你们中有人知道如何使用它,请告诉我。

elasticsearch go客户端Github repo包含了一个如何模拟elaticsearch客户端的官方示例。它基本上涉及到使用一个截断HTTP传输的配置来调用NewClient

client, err := elasticsearch.NewClient(elasticsearch.Config{
    Transport: &mocktrans,
})

我发现创建Mock/Dumy ES客户端主要有三种方法。我的回复不包括针对真正的Elasticsearch集群的集成测试。

  1. 您可以遵循本文,以便使用httptest模拟来自Rest调用的响应。服务器,最终创建一个虚拟弹性。客户端结构

  2. 正如包作者在这个链接中提到的,你可以在;指定一个有两个实现的接口:一个使用真正的ES集群,另一个使用测试中使用的回调。下面是一个让你开始的例子:;


type Searcher interface {
    Search(context.Context, SearchRequest) (*SearchResponse, error)
}
// ESSearcher will be used with a real ES cluster.
type ESSearcher struct {
    client *elastic.Client
}
func (s *ESSearcher) Search(ctx context.Context, req SearchRequest) (*SearchResponse, error) {
    // Use s.client to run against real ES cluster and perform a search
}
// MockedSearcher can be used in testing.
type MockedSearcher struct {
    OnSearch func(context.Context, SearchRequest) (*SearchResponse, error)
}
func (s *ESSearcher) Search(ctx context.Context, req SearchRequest) (*SearchResponse, error) {
    return s.OnSearch(ctx, req)
}
  1. 最后,正如作者在同一链接中提到的那样,你可以"在测试时运行一个真正的Elasticsearch集群。一个特别好的方法可能是在测试过程中用github.com/ory/docketset之类的东西启动ES集群
package search
import (
    "context"
    "fmt"
    "log"
    "os"
    "testing"
    "github.com/olivere/elastic/v7"
    "github.com/ory/dockertest/v3"
    "github.com/ory/dockertest/v3/docker"
)
// client will be initialize in TestMain
var client *elastic.Client
func TestMain(m *testing.M) {
    pool, err := dockertest.NewPool("")
    if err != nil {
        log.Fatalf("unable to create new pool: %v", err)
    }
    options := &dockertest.RunOptions{
        Repository: "docker.elastic.co/elasticsearch/elasticsearch-oss",
        Tag:        "7.8.0",
        PortBindings: map[docker.Port][]docker.PortBinding{
            "9200": {{HostPort: "9200"}},
        },
        Env: []string{
            "cluster.name=elasticsearch",
            "bootstrap.memory_lock=true",
            "discovery.type=single-node",
            "network.publish_host=127.0.0.1",
            "logger.org.elasticsearch=warn",
            "ES_JAVA_OPTS=-Xms1g -Xmx1g",
        },
    }
    resource, err := pool.RunWithOptions(options)
    if err != nil {
        log.Fatalf("unable to ES: %v", err)
    }
    endpoint := fmt.Sprintf("http://127.0.0.1:%s", resource.GetPort("9200/tcp"))
    if err := pool.Retry(func() error {
        var err error
        client, err = elastic.NewClient(
            elastic.SetURL(endpoint),
            elastic.SetSniff(false),
            elastic.SetHealthcheck(false),
        )
        if err != nil {
            return err
        }
        _, _, err = client.Ping(endpoint).Do(context.Background())
        if err != nil {
            return err
        }
        return nil
    }); err != nil {
        log.Fatalf("unable to connect to ES: %v", err)
    }
    code := m.Run()
    if err := pool.Purge(resource); err != nil {
        log.Fatalf("unable to stop ES: %v", err)
    }
    os.Exit(code)
}
func TestAgainstRealCluster(t *testing.T) {
    // You can use "client" variable here
    // Example code:
    exists, err := client.IndexExists("cities-test").Do(context.Background())
    if err != nil {
        t.Fatal(err)
    }
    if !exists {
        t.Fatal("expected to find ES index")
    }
}

func CheckBucketExists(name string, client *elastic.Client) bool {

声明CCD_ 8期望CCD_。

线路:

m := MockClient{}
m.On("IndexExists", "thisuri").Return(true) 
r := CheckBucketExists("thisuri", m)

将CCD_ 10传递给CCD_。

这导致了类型冲突。

也许您需要将github.com/olivere/elastic导入到测试文件中,并执行以下操作:

m := &elastic.Client{}

而不是

m := MockClient{}

但我不能100%确定你想做什么。

最新更新