Возврат макета из функции пакета - PullRequest
0 голосов
/ 19 сентября 2019

Я довольно новичок в Go, и у меня возникли некоторые проблемы с написанием тестов, в частности насмешка над ответом функции пакета.

Я пишу библиотеку оболочки для github.com/go-redis/redis.На данный момент он действительно имеет только лучшие ошибки для сбоев, но он будет расширен за счет отслеживания statsd дальше, но я отвлекся ...

У меня есть следующий пакет go, который я создал

package myredis

import (
    "time"

    "github.com/go-redis/redis"
    errors "github.com/pkg/errors"
)

var newRedisClient = redis.NewClient

// Options - My Redis Connection Options
type Options struct {
    *redis.Options
    DefaultLifetime time.Duration
}

// MyRedis - My Redis Type
type MyRedis struct {
    options Options
    client  *redis.Client
}

// Connect - Connects to the Redis Server. Returns an error on failure
func (r *MyRedis) Connect() error {

    r.client = newRedisClient(&redis.Options{
        Addr:     r.options.Addr,
        Password: r.options.Password,
        DB:       r.options.DB,
    })

    _, err := r.client.Ping().Result()
    if err != nil {
        return errors.Wrap(err, "myredis")
    }

    return nil
}

Моя проблема в том, что я хочу, чтобы redis.NewClient вернул макет.Это тестовый код, который я написал, но он не работает:

package myredis

import (
    "testing"

    "github.com/go-redis/redis"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
)

type redisStatusCmdMock struct {
    mock.Mock
}

func (m *redisStatusCmdMock) Result() (string, error) {
    args := m.Called()
    return args.Get(0).(string), args.Error(1)
}

type redisClientMock struct {
    mock.Mock
}

func (m *redisClientMock) Ping() redis.StatusCmd {
    args := m.Called()
    return args.Get(0).(redis.StatusCmd)
}

func TestConnect(t *testing.T) {
    assert := assert.New(t)

    old := newRedisClient
    defer func() { newRedisClient = old }()

    newRedisClient = func(options *redis.Options) *redis.Client {
        assert.Equal("127.0.0.1:1001", options.Addr)
        assert.Equal("password", options.Password)
        assert.Equal(1, options.DB)

        statusCmdMock := new(redisStatusCmdMock)
        statusCmdMock.On("Result").Return("success", nil)

        clientMock := new(redisClientMock)
        clientMock.On("Ping").Return(statusCmdMock)

        return clientMock
    }

    options := Options{}
    options.Addr = "127.0.0.1:1001"
    options.Password = "password"
    options.DB = 1

    r := MyRedis{options: options}

    result, err := r.Connect()

    assert.Equal("success", result)
    assert.Equal(nil, err)
}

Я получаю следующую ошибку: cannot use clientMock (type *redisClientMock) as type *redis.Client in return argument.Я думаю, что прочитал, что мне нужно смоделировать все функции redis.Client, чтобы иметь возможность использовать его в качестве насмешки в этом случае, но так ли это на самом деле?Кажется, это излишне, и я должен быть в состоянии сделать это каким-то образом.Как мне заставить этот тест работать или мне нужно реструктурировать мой код, чтобы его было легче написать?

1 Ответ

1 голос
/ 20 сентября 2019

redis.Client - это тип struct , а в Go типы структур просто , а не , могут быть смоделированы.Однако интерфейсы в Go являются mockable, поэтому вы можете определить свой собственный «newredisclient» func, который вместо возврата структуры возвращает интерфейс.А так как интерфейсы в Go удовлетворяются неявно, вы можете определить свой интерфейс так, чтобы он был реализован с помощью redis.Client из коробки.

type RedisClient interface {
    Ping() redis.StatusCmd
    // include any other methods that you need to use from redis
}

func NewRedisCliennt(options *redis.Options) RedisClient {
    return redis.NewClient(options)
}

var newRedisClient = NewRedisClient

Если вы также хотите смоделировать возвращаемое значение изPing(), вам нужно проделать немного больше работы.

// First define an interface that will replace the concrete redis.StatusCmd.
type RedisStatusCmd interface {
    Result() (string, error)
    // include any other methods that you need to use from redis.StatusCmd
}

// Have the client interface return the new RedisStatusCmd interface
// instead of the concrete redis.StatusCmd type.
type RedisClient interface {
    Ping() RedisStatusCmd
    // include any other methods that you need to use from redis.Client
}

Теперь *redis.Client не больше удовлетворяет интерфейсу RedisClient, поскольку тип возвращаемого значения Ping()разные.Обратите внимание, что не имеет значения, что тип результата redis.Client.Ping() удовлетворяет типу интерфейса, возвращаемому RedisClient.Ping(), важно то, что сигнатуры методов различны и, следовательно, их типы различны.

Чтобы исправить этоВы можете определить тонкую оболочку, которая использует *redis.Client напрямую, а также удовлетворяет новому интерфейсу RedisClient.

type redisclient struct {
    rc *redis.Client
}

func (c *redisclient) Ping() RedisStatusCmd {
    return c.rc.Ping()
}

func NewRedisCliennt(options *redis.Options) RedisClient {
    // here wrap the *redis.Client into *redisclient
    return &redisclient{redis.NewClient(options)}
}

var newRedisClient = NewRedisClient
...