从包函数返回 Mock
Returning a Mock from a package function
我是 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
到 return 一个模拟。这是我写的测试代码,但它不起作用:
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
的所有功能,以便能够在这种情况下将其用作模拟,但事实真的如此吗?这似乎有点矫枉过正,我应该能够以某种方式做到这一点。我该如何让这个测试工作,或者我是否需要重构我的代码以便更容易编写测试?
redis.Client
是一个 struct 类型,在 Go 中结构类型只是 not 可模拟的。然而 Go 中的接口 是 可模拟的,所以你可以做的是定义你自己的 "newredisclient" 函数,而不是 returning 结构 returns一个界面。由于 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()
模拟 return 值,您需要做更多的工作。
// 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()
的 return 类型不同。请注意,redis.Client.Ping()
的结果类型是否满足由 RedisClient.Ping()
编辑的接口类型 return 并不重要,重要的是方法签名不同,因此它们的类型也不同。
要解决此问题,您可以定义一个直接使用 *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
我是 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
到 return 一个模拟。这是我写的测试代码,但它不起作用:
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
的所有功能,以便能够在这种情况下将其用作模拟,但事实真的如此吗?这似乎有点矫枉过正,我应该能够以某种方式做到这一点。我该如何让这个测试工作,或者我是否需要重构我的代码以便更容易编写测试?
redis.Client
是一个 struct 类型,在 Go 中结构类型只是 not 可模拟的。然而 Go 中的接口 是 可模拟的,所以你可以做的是定义你自己的 "newredisclient" 函数,而不是 returning 结构 returns一个界面。由于 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()
模拟 return 值,您需要做更多的工作。
// 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()
的 return 类型不同。请注意,redis.Client.Ping()
的结果类型是否满足由 RedisClient.Ping()
编辑的接口类型 return 并不重要,重要的是方法签名不同,因此它们的类型也不同。
要解决此问题,您可以定义一个直接使用 *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