我在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集群的集成测试。
-
您可以遵循本文,以便使用httptest模拟来自Rest调用的响应。服务器,最终创建一个虚拟弹性。客户端结构
-
正如包作者在这个链接中提到的,你可以在;指定一个有两个实现的接口:一个使用真正的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)
}
- 最后,正如作者在同一链接中提到的那样,你可以"在测试时运行一个真正的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%确定你想做什么。