问题描述
我刚接触Go,在编写测试时遇到了一些问题,特别是模拟了包函数的响应.
I'm fairly new to Go and I'm having some issues with writing tests, specifically mocking the response of a package function.
我正在为 github.com/go-redis/redis
编写包装器库.目前,它确实确实存在更好的故障错误,但是它将通过statsd跟踪进一步扩展,但是我离题了……
I'm writing an wrapper lib for github.com/go-redis/redis
. At the moment it only really has better errors for failures, but it will be expanded with statsd tracking further down the line, but I digress...
我有以下已创建的go程序包
I have the following go package that I have created
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
返回一个模拟.这是我编写的测试代码,但是没有用:
My problem is that I want redis.NewClient
to return a mock. This is the test code that I wrote, but it's not working:
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)
}
我收到以下错误:不能在返回参数中使用clientMock(* redisClientMock类型)作为* redis.Client类型
.我想我读到我需要模拟 redis.Client
的所有功能,以便能够在这种情况下将其用作模拟,但事实确实如此吗?看来这太过分了,我应该能够以某种方式做到这一点.我该如何使该测试正常工作,或者我需要重组代码以便编写测试更容易?
I get the following error: cannot use clientMock (type *redisClientMock) as type *redis.Client in return argument
. I think I read that I need to mock all the functions of redis.Client
in order to be able to use it as a mock in this case, but is that really the case? That seems like it's overkill and I should be able to do this in some way. How do I go about getting this test to work, or do I need to restructure my code so that it's easier to write the test?
推荐答案
redis.Client
是 struct 类型,在Go结构类型中, not 可模拟的.但是Go 中的接口是可模拟的,因此您可以定义自己的"newredisclient"函数,而不是返回结构来返回接口.而且由于Go中的接口是隐式满足的,因此您可以定义您的接口,以便由redis.Client开箱即用地实现.
redis.Client
is a struct type and in Go struct types are simply not mockable. However interfaces in Go are mockable, so what you can do is to define your own "newredisclient" func that instead of returning a struct returns an interface. And since interfaces in Go are satisfied implicitly you can define your interface such that it will be implemented by redis.Client out of the box.
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()
的返回值,则需要做更多的工作.
If you also want to mock the return value from Ping()
, you need to do a bit more work.
// 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()
返回的接口类型并不重要,重要的是方法签名不同,因此它们的类型也不同.
Now *redis.Client
does not satisfy the RedisClient
interface anymore because the return type of Ping()
is different. Note that it doesn't matter that the result type of redis.Client.Ping()
satisfies the interface type returned by RedisClient.Ping()
, what matters is that the method signatures are different and therefore their types are different.
要解决此问题,您可以定义一个直接使用 * redis.Client
的精简包装,并满足新的 RedisClient
接口.
To fix this you can define a thin wrapper that uses *redis.Client
directly and also satisfies the new RedisClient
interface.
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
这篇关于从包函数返回模拟的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!