在Golang中测试/模拟第3方软件包

I'm new to Golang and have been taking a TDD approach while learning the language. I've been getting along okay yet I find testing third party packages quite clunky, which leads me to believe that I've been taking the wrong approach.

The specific case I'm having trouble with is mocking a Redis client for error handling. The approach I've taken is to create my own interface, and the implementation wraps the clients methods that I want to use.

type Redis interface {
    Get(key string) (string, error)
}

type RedisClient struct {
    client *redis.Client
}

func (redisClient *RedisClient) New(client *redis.Client) *RedisClient {
    redisClient.client = client

    return redisClient
}

func (redisClient *RedisClient) Get(key string) (string, error) {
    return redisClient.client.Get(key).Result()
}

I can then create a mock which implements that same interface to return whichever values I specify, particularly for testing error handling.

I've hit a roadblock where a specific method on the client to perform transactions (MULTI) returns another interface belonging to that package. What would I do in this scenario? Implementing that interface myself seems out of the question.

Similarly, as usage of this client grows, my own implementation can grow to the point that it implements the whole interface to Redis - this seems to go against the whole idea of delegating this out to an external dependency.

Is there a better way to test third-party packages like this, for things such as error handling?

One approach would be to create a type that focuses on what you want to accomplish instead on what methods of the client you are using.

Let's say all you want is a storage to save and fetch users, you could imagine an interface like this:

type UserStore interface {
  SaveUser(*User) error
  GetUserByID(id string) (*User, error)
  SearchUsers(query string) ([]User, error)
}

You could then implement a Redis version of this storage and call whatever client methods you want inside, it doesn't matter. You can even implement one in PostgreSQL or whatever. Also, mocking is way easier with this approach since you all you need to do is to implement this interface instead of the Redis one.

Here is an example of a mock version of this interface:

type UserStoreMock struct {
  SaveUserFn func (*User) error
  SaveUserInvoked bool
  ...
}

func (m *UserStoreMock) SaveUser(u *User) error {
  m.SaveUserInvoked = true

  if m.SaveUserFn != nil {
    return m.SaveUserFn(u)
  }

  return nil
}
...

You can then use this mock in tests like this:

var m UserStoreMock

m.SaveUserFn = func(u *User) error {
  if u.ID != "123" {
    t.Fail("Bad ID")
  }

  return ErrDuplicateError
}
...