golang:Redis Mock 测试模板
·
概述
本模板提供了一个使用 redismock/v8 库进行 Redis 相关测试的标准结构,适用于需要模拟 Redis 操作的单元测试场景。
依赖
import (
"context"
"fmt"
"testing"
"time"
"github.com/go-redis/redis/v8"
"github.com/go-redis/redismock/v8"
"github.com/stretchr/testify/assert"
)
模板结构
1. Mock Redis 结构体
// mockRedis 实现 Redis 接口的 mock 结构体
type mockRedis struct {
redis.UniversalClient
}
// 如果你的业务接口中定义了额外的方法(如 Lock),也需要在这里实现
// Lock 实现 RedisLock 接口的 Lock 方法
func (m *mockRedis) Lock(ctx context.Context, key string, expires time.Duration) (customerredis.UnLocker, error) {
return nil, nil
}
说明:如果你的业务代码直接依赖 redis.UniversalClient 接口,则无需自定义 mockRedis,可以直接使用
redismock.NewClientMock() 返回的 *redis.Client。
2. 测试函数模板
以下是一个完整的测试函数模板,包含测试用例表结构、mock 设置、测试执行和结果验证。
// TestXXX 测试描述
func TestXXX(t *testing.T) {
t.Parallel()
// 创建测试实例
domain := &YourDomainImpl{}
// 测试用例
testCases := []struct {
name string
// 测试参数
limitRules []YourLimitRule
customerID int64
// 预期结果
expected YourResponse
expectedErr bool
// Mock 设置函数
setupMock func(*YourDomainImpl, redismock.ClientMock, int64, time.Time)
}{
{
name: "测试场景1",
limitRules: []YourLimitRule{
{CycleType: 1, MaxTimes: 5}, // 按天
{CycleType: 2, MaxTimes: 3}, // 按月
},
customerID: 1001,
expected: YourResponse{
IsRestricted: true,
RestrictType: 2,
CurrentCount: 4,
MaxTimes: 3,
},
expectedErr: false,
setupMock: func(domain *YourDomainImpl, mock redismock.ClientMock, customerID int64, now time.Time) {
// 设置 Redis Get 操作的 mock
cycleInfo := domain.calculateCycle(2, now) // 按月
cacheKey := domain.buildCacheKey(customerID, 2, cycleInfo.Start)
mock.ExpectGet(cacheKey).SetVal("4") // 返回达到限制的值
},
},
{
name: "测试场景2 - Redis返回nil",
limitRules: []YourLimitRule{
{CycleType: 2, MaxTimes: 5}, // 按月
{CycleType: 1, MaxTimes: 3}, // 按天
},
customerID: 1004,
expected: YourResponse{
IsRestricted: false,
RestrictType: 0,
CurrentCount: 0,
MaxTimes: 0,
},
expectedErr: false,
setupMock: func(domain *YourDomainImpl, mock redismock.ClientMock, customerID int64, now time.Time) {
// 为所有规则设置mock,都返回redis.Nil
limitRules := []YourLimitRule{
{CycleType: 2, MaxTimes: 5}, // 按月
{CycleType: 1, MaxTimes: 3}, // 按天
}
for _, rule := range limitRules {
cycleInfo := domain.calculateCycle(rule.CycleType, now)
cacheKey := domain.buildCacheKey(customerID, rule.CycleType, cycleInfo.Start)
mock.ExpectGet(cacheKey).SetErr(redis.Nil)
}
},
},
{
name: "测试场景3 - Redis查询失败",
limitRules: []YourLimitRule{
{CycleType: 2, MaxTimes: 5}, // 按月
},
customerID: 1005,
expected: YourResponse{},
expectedErr: true,
setupMock: func(domain *YourDomainImpl, mock redismock.ClientMock, customerID int64, now time.Time) {
// 模拟Redis Get操作失败
cycleInfo := domain.calculateCycle(2, now)
cacheKey := domain.buildCacheKey(customerID, 2, cycleInfo.Start)
mock.ExpectGet(cacheKey).SetErr(fmt.Errorf("Redis连接失败"))
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// 创建Redis mock
client, mock := redismock.NewClientMock()
mockRedis := &mockRedis{UniversalClient: client}
// 模拟Redis Get操作
ctx := context.Background()
now := time.Now()
// 设置mock预期
tc.setupMock(domain, mock, tc.customerID, now)
// 准备请求参数
req := &YourRequest{
CustomerID: tc.customerID,
ShopID: 1,
CardType: 1,
}
// 执行测试
result, err := domain.yourMethod(ctx, mockRedis, tc.limitRules, req)
// 验证结果
if tc.expectedErr {
assert.Error(t, err)
assert.Contains(t, err.Error(), "错误信息")
} else {
assert.NoError(t, err)
assert.Equal(t, tc.expected.IsRestricted, result.IsRestricted)
assert.Equal(t, tc.expected.RestrictType, result.RestrictType)
if tc.expected.IsRestricted {
assert.Equal(t, tc.expected.CurrentCount, result.CurrentCount)
assert.Equal(t, tc.expected.MaxTimes, result.MaxTimes)
}
}
// 验证所有的mock预期都被满足
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("未满足的mock预期: %v", err)
}
})
}
}
使用说明
- 替换占位符:
YourDomainImpl- 替换为你的领域、服务等实现结构体yourMethod- 替换为你要测试的方法名TestXXX- 替换为你的测试函数名
- 自定义测试用例:
- 根据具体业务场景添加或修改测试用例
- 为每个测试用例设置适当的
setupMock函数来模拟 Redis 操作
- 扩展模拟操作:
除了ExpectGet,还可以使用其他 Redis 操作的模拟,如:mock.ExpectSet(key, value, expiration)- 模拟 SET 操作mock.ExpectIncr(key)- 模拟 INCR 操作mock.ExpectExpire(key, expiration)- 模拟 EXPIRE 操作- 支持管道(pipelining)和事务(Tx)的模拟。
最佳实践
- 并行测试:使用
t.Parallel()提高测试执行效率 - 完整的错误处理:测试 Redis 操作失败的场景
- 边界情况:测试 Redis 返回 nil 的情况
- 验证 mock 预期:使用
mock.ExpectationsWereMet()确保所有 mock 操作都被调用 - 清晰的测试用例命名:使用描述性的测试用例名称,便于理解测试场景
示例场景
1. 测试限流场景
// 测试用例:按月规则超过限制
{
name: "按月规则超过限制",
limitRules: []YourLimitRule{
{CycleType: 1, MaxTimes: 5}, // 按天
{CycleType: 2, MaxTimes: 3}, // 按月
},
customerID: 1001,
expected: YourResponse{
IsRestricted: true,
RestrictType: 2,
CurrentCount: 4,
MaxTimes: 3,
},
expectedErr: false,
setupMock: func(domain *YourDomainImpl, mock redismock.ClientMock, customerID int64, now time.Time) {
// 只为按月规则设置mock,返回达到限制的值
cycleInfo := domain.calculateCycle(2, now) // 按月
cacheKey := domain.buildCacheKey(customerID, 2, cycleInfo.Start)
mock.ExpectGet(cacheKey).SetVal("4") // 达到限制
},
}
2. 测试缓存不存在场景
// 测试用例:Redis返回nil(缓存中没有数据)
{
name: "Redis返回nil(缓存中没有数据)",
limitRules: []YourLimitRule{
{CycleType: 2, MaxTimes: 5}, // 按月
{CycleType: 1, MaxTimes: 3}, // 按天
},
customerID: 1004,
expected: YourResponse{
IsRestricted: false,
RestrictType: 0,
CurrentCount: 0,
MaxTimes: 0,
},
expectedErr: false,
setupMock: func(domain *YourDomainImpl, mock redismock.ClientMock, customerID int64, now time.Time) {
// 为所有规则设置mock,都返回redis.Nil
limitRules := []YourLimitRule{
{CycleType: 2, MaxTimes: 5}, // 按月
{CycleType: 1, MaxTimes: 3}, // 按天
}
for _, rule := range limitRules {
cycleInfo := domain.calculateCycle(rule.CycleType, now)
cacheKey := domain.buildCacheKey(customerID, rule.CycleType, cycleInfo.Start)
mock.ExpectGet(cacheKey).SetErr(redis.Nil)
}
},
}
3. 测试Redis错误场景
// 测试用例:Redis查询失败
{
name: "Redis查询失败",
limitRules: []YourLimitRule{
{CycleType: 2, MaxTimes: 5}, // 按月
},
customerID: 1005,
expected: YourResponse{},
expectedErr: true,
setupMock: func(domain *YourDomainImpl, mock redismock.ClientMock, customerID int64, now time.Time) {
// 模拟Redis Get操作失败
cycleInfo := domain.calculateCycle(2, now)
cacheKey := domain.buildCacheKey(customerID, 2, cycleInfo.Start)
mock.ExpectGet(cacheKey).SetErr(fmt.Errorf("Redis连接失败"))
},
}
总结
本模板提供了一个完整的 Redis Mock 测试框架,可以帮助开发者快速编写针对 Redis 操作的单元测试。通过使用 redismock/v8 库,我们可以方便地模拟各种 Redis 操作场景,包括正常返回、返回 nil 以及操作失败等情况,从而确保代码在各种 Redis 状态下都能正确处理。
使用此模板时,只需根据具体业务场景替换相应的占位符,并添加适当的测试用例即可。
更多推荐




所有评论(0)