You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Golang中用Testify Mock Redis客户端进行单元测试?

问题:Gin API中Mock Redis单例客户端的困境

背景

我有一个基于gin的简单Rest API,实现了Redis数据库的CRUD操作。想在单元测试中mock Redis客户端(github.com/go-redis/redis/v9),但因为有OOP背景,可能错误套用了模式。

路由代码(internal/api.go)

func GetRouter() *gin.Engine {
    router := gin.Default()
    ...
    router.GET("/session/:sessionid", getSession)
    return router
}

控制器代码(internal/controlers.go)

func getSession(c *gin.Context) {
    client := redis_client.GetClient()
    sessionid := c.Param("sessionid")
    val, err := client.Get(c, sessionid).Result()
    if err == redis.Nil {
        fmt.Printf("key %s not exist", sessionid)
        c.JSON(404, fmt.Sprintf("session %s not found", sessionid))
    } else if err != nil {
        fmt.Printf("unkown error %s", err)
        c.JSON(500, fmt.Sprintf(`{"error":"%s"}`, err))
    } else {
        c.JSON(200, val)
    }
}

Redis单例客户端(pkg/redis/redis.go)

var redisClient *redis.Client

func GetClient() *redis.Client {
    if redisClient == nil {
        redisClient = redis.NewClient(&redis.Options{
            Addr:     os.Getenv("REDIS_HOST"),
            Password: os.Getenv("REDIS_PWD"),
            DB:       0,                     
        })
    }
    return redisClient
}

测试场景

我尝试用github.com/stretchr/testify/mock来mock redis.Client.Get或redis.StringCmd.Result(实际调用Redis的地方),但无论怎么mock,最终还是会调用localhost:6379。

Mock尝试代码

import (
    ....
    "testing"
    "github.com/stretchr/testify/mock"
)

type MockedRedisConn struct {
    mock.Mock
}

func (m *MockedRedisConn) Get(ctx context.Context, key string) *redis.StringCmd {
    ret := m.Called(ctx, key)
    var r0 *redis.StringCmd
    if ret.Get(0) != nil {
        r0 = ret.Get(0).(*redis.StringCmd)
    }
    return r0
}

func Test_Sessions(t *testing.T) {
    r := api.GetRouter()
    redisConn := new(MockedRedisConn)
    var ctx = context.Background()
    t.Run("Should return the session", func(t *testing.T) {
        //mocks
        strCmd := redis.NewStringCmd(ctx)
        strCmd.SetVal(`{"session":""}`)
        redisConn.On("Get", mock.AnythingOfType("*gin.Context"), "00000000").Return(strCmd)

        //call
        req, _ := http.NewRequest("GET", "/session/00000000", nil)

        w := httptest.NewRecorder()
        r.ServeHTTP(w, req)

        responseData, _ := ioutil.ReadAll(w.Body)
        assert.Equal(t, `{"session":""}`, string(responseData))
        assert.Equal(t, http.StatusOK, w.Code)
    })
}

疑问

我发现大多数mock示例中,mock对象都是被测函数的参数,但我的情况不是这样——我没办法把getSession里的客户端替换成MockedRedisConn(类似猴子补丁的方式)。想请教:

  • 这种单例客户端的设置方式是否兼容mock?
  • 是否应该从*gin.Context加载客户端以便测试时替换?

内容的提问来源于stack exchange,提问作者zar3bski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 21:51:16