从包函数返回 Mock

我对 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)


    }


}


我收到以下错误:cannot use clientMock (type *redisClientMock) as type *redis.Client in return argument。我想我读到我需要模拟 的所有功能,redis.Client以便能够在这种情况下将其用作模拟,但事实真的是这样吗?这似乎太过分了,我应该能够以某种方式做到这一点。我该如何让这个测试工作,或者我是否需要重组我的代码以便更容易编写测试?


拉丁的传说
浏览 84回答 1
1回答

千巷猫影

redis.Client是一种结构类型,并且在 Go 中结构类型根本不可模拟。然而 Go 中的接口是可模拟的,所以你可以做的是定义你自己的“newredisclient”函数,而不是返回一个结构,而是返回一个接口。由于 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
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go