Для начала я бы вытащил создание модели. Тип пользователя, поскольку это объект высокого уровня, который определяет политику, согласно которой идентификаторы пользователей будут uuid. Вы не хотите, чтобы это было связано с нижним уровнем, где вы взаимодействуете с поиском elasti c. Я бы просто передал пользователя. Также вы можете проверить, созданы ли пользователи, как ожидалось. Наличие этого logi c в AddUser усложнит тестирование.
Далее я бы использовал интерфейс для передачи зависимости от elasti c .Client. Это позволит вам поиздеваться над ним и увидеть, что AddUser использует эту зависимость, как и ожидалось (т.е. добавляет пользователя).
Возможно, вы даже захотите создать новый тип, который будет содержать интерфейсы для ваших зависимостей низкого уровня и предоставляет AddUser как метод. Затем вы можете создать интерфейс, в котором есть AddUser, чтобы отделить клиентов от интерфейса от деталей низкого уровня.
Когда имитировать
Вот пример. Я думаю, что ссылка выше предлагает хороший совет по ограничению этого архитектурными границами. Вы видите, как это может быстро выйти из-под контроля.
package main
import (
"context"
"fmt"
"github.com/olivere/elastic"
)
type User struct {}
type Repo interface {
AddUser(User) error
}
type ElasticClient interface {
Index() ElasticIndex
}
type ElasticIndex interface {
Index(string) ElasticIndex
BodyString(string) ElasticIndex
Do(context.Context) (*elastic.IndexResponse, error)
}
type MockElasticClient struct {
Log *[]string
index MockElasticIndex
}
type MockElasticIndex struct {
Log *[]string
}
func NewMockElasticClient() *MockElasticClient {
var log []string
return &MockElasticClient{
Log: &log,
index: MockElasticIndex{&log},
}
}
func (c *MockElasticClient) Index() ElasticIndex {
*c.Log = append(*c.Log, "Client.Index()")
return &c.index
}
func (i *MockElasticIndex) Index(s string) ElasticIndex {
*i.Log = append(*i.Log, fmt.Sprintf("Index.Index(%s)", s))
return i
}
func (i *MockElasticIndex) BodyString(s string) ElasticIndex {
*i.Log = append(*i.Log, fmt.Sprintf("Index.BodyString(%s)", s))
return i
}
func (i *MockElasticIndex) Do(ctx context.Context) (*elastic.IndexResponse, error) {
*i.Log = append(*i.Log, "Index.Do()")
return nil, nil
}
// Use a wrapped client to deal with the fact that elastic
// returns their own types and not interfaces.
type WrappedElasticClient struct {
wrapped *elastic.Client
}
type WrappedElasticIndexService struct {
wrapped *elastic.IndexService
}
func (c WrappedElasticClient) Index() ElasticIndex {
return WrappedElasticIndexService{c.wrapped.Index()}
}
func (i WrappedElasticIndexService) Index(s string) ElasticIndex {
i.wrapped.Index(s)
return i
}
func (i WrappedElasticIndexService) BodyString(s string) ElasticIndex {
i.BodyString(s)
return i
}
func (i WrappedElasticIndexService) Do(ctx context.Context) (*elastic.IndexResponse, error) {
return i.wrapped.Do(ctx)
}
type ElasticRepo struct {
ctx context.Context
client ElasticClient
}
func (r ElasticRepo) AddUser(u User) error {
s, err := utils.ParseToString(u)
if err != nil {
return err
}
_, err = r.client.Index().
Index("some index").
BodyString(s).
Do(r.ctx)
if err != nil {
fmt.Println("Error Storing the User")
return err
}
return err
}
func main() {
m := NewMockElasticClient()
r := ElasticRepo{
ctx: nil,
client: m,
//client: WrappedElasticClient{&elastic.Client{}},
}
r.AddUser(User{})
fmt.Println(m.Log)
}