Skip to content

redis mock解决方案

Jakegogo edited this page Oct 17, 2024 · 2 revisions

redis mock

其实是想mock dial image

xxx 3-31 下午 4:51 应该怎么写呢

xxx 3-31 下午 4:51 image

xxx 3-31 下午 4:52 mock return 的时候,不知道要怎么构造 redis.Conn 的实例

方案一:

mocker 3-31 下午 4:53

package redigo_test

import (
	"errors"
	"strconv"
	"testing"

	"git.code.oa.com/goom/mocker_examples/redigo"

	"git.code.oa.com/goom/mocker"
	"github.com/gomodule/redigo/redis"
	"github.com/stretchr/testify/suite"
)

// TestUnitGetTelFromRedis 测试入口
func TestUnitGetTelFromRedis(t *testing.T) {
	suite.Run(t, new(GetTelFromRedisTestSuite))
}

// PhpToolTestSuite测试套件
type GetTelFromRedisTestSuite struct {
	suite.Suite
	mocker  *mocker.Builder
	fakeErr error
}

func (s *GetTelFromRedisTestSuite) SetupTest() {
	// patches初始化
	s.mocker = mocker.Create()
	_, err := strconv.ParseUint("-101", 10, 64)
	s.fakeErr = err
}

// TestUnitGetTelFromRedis 测试函数GetTelFromRedis
func (s *GetTelFromRedisTestSuite) TestUnitGetTelFromRedis() {
	s.Run("success", func() {
		testCases := s.TestUnitGetTelFromRedisCase()

		for _, tc := range testCases {
			s.Run(tc.name, func() {
				tc.prepare()
				// 获取参数
				param := tc.params.(string)
				// 执行
				result, err := redigo.GetTelFromRedis(param)
				// 断言
				s.Equal((tc.wantResult).([]interface{})[0], result)
				s.Equal((tc.wantResult).([]interface{})[1], err)
				// 重置
				s.mocker.Reset()
			})
		}

	})
}

func (s *GetTelFromRedisTestSuite) TestUnitGetTelFromRedisCase() []Case {
	return []Case{
		{
			name: "key not exists",
			prepare: func() {
				conn := (redis.Conn)(nil)
				s.mocker.Interface(&conn).Method("Do").
					As(func(ctx *mocker.IContext, commandName string, args ...interface{}) (reply interface{}, err error) {
						return nil, nil
					}).Return("101", redis.ErrNil)

				s.mocker.Interface(&conn).Method("Close").
					Apply(func(ctx *mocker.IContext) (err error) {
						return nil
					})

				s.mocker.Func(redis.Dial).Apply(func(network, address string, options ...redis.DialOption) (redis.Conn, error) {
					return conn, nil
				})
			},
			params: "key",
			wantResult: []interface{}{
				uint64(0),
				errors.New("key not exists"),
			},
		},
	}
}

// Case 测试case
type Case struct {
	name       string
	prepare    func()
	params     interface{}
	eval       func(tt Case) []interface{}
	wantResult interface{}
}

这个example可以跑通

xxx 3-31 下午 4:54 conn := (redis.Conn)(nil) 一开始是这样用的,好像有问题。我再看下

方案二:

mocker 3-31 下午 4:58 也可以用这种方式

package codetool

import (
	"errors"
	"testing"
	"time"

	"git.code.oa.com/goom/mocker"
	"github.com/gomodule/redigo/redis"
)

func TestIsNumeric(t *testing.T) {
	tests := []struct {
		name       string
		arg        interface{}
		wantResult bool
	}{
		{
			name:       "int",
			arg:        0,
			wantResult: true,
		},
		{
			name:       "uint",
			arg:        uint(0),
			wantResult: true,
		},
		{
			name:       "int8",
			arg:        int8(0),
			wantResult: true,
		},
		{
			name:       "uint8",
			arg:        uint8(0),
			wantResult: true,
		},
		{
			name:       "int16",
			arg:        int16(0),
			wantResult: true,
		},
		{
			name:       "uint16",
			arg:        uint16(0),
			wantResult: true,
		},
		{
			name:       "int32",
			arg:        int32(0),
			wantResult: true,
		},
		{
			name:       "uint32",
			arg:        uint32(0),
			wantResult: true,
		},
		{
			name:       "int64",
			arg:        int64(0),
			wantResult: true,
		},
		{
			name:       "uint64",
			arg:        uint64(0),
			wantResult: true,
		},
		{
			name:       "float32",
			arg:        float32(0),
			wantResult: true,
		},
		{
			name:       "float64",
			arg:        float64(0),
			wantResult: true,
		},
		{
			name:       "complex64",
			arg:        complex64(0),
			wantResult: true,
		},
		{
			name:       "complex128",
			arg:        complex128(0),
			wantResult: true,
		},
		{
			name:       "string",
			arg:        "3.2",
			wantResult: true,
		},
		{
			name:       "invalid type",
			arg:        []string{},
			wantResult: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if gotResult := IsNumeric(tt.arg); gotResult != tt.wantResult {
				t.Errorf("IsNumeric() = %v, want %v", gotResult, tt.wantResult)
			}
		})
	}
}

func TestIsStringNumeric(t *testing.T) {
	tests := []struct {
		name string
		arg  string
		want bool
	}{
		{
			name: "empty string",
			arg:  "",
			want: false,
		},
		{
			name: "invalid string number including -",
			arg:  "3-2",
			want: false,
		},
		{
			name: "invalid string number including two point",
			arg:  "3.2.1",
			want: false,
		},
		{
			name: "invalid string number with alphabet",
			arg:  "abc",
			want: false,
		},
		{
			name: "valid string number",
			arg:  "-3.2",
			want: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := IsStringNumeric(tt.arg); got != tt.want {
				t.Errorf("IsStringNumeric() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestStrtotime(t *testing.T) {
	tests := []struct {
		name    string
		timeStr string
		want    int64
	}{
		{
			name:    "empty time string",
			timeStr: "",
			want:    0,
		},
		{
			name:    "parse time error",
			timeStr: "test",
			want:    0,
		},
		{
			name:    "parse time success",
			timeStr: "2021-03-02 14:30:00",
			want:    1614666600,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Strtotime(tt.timeStr); got != tt.want {
				t.Errorf("Strtotime() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestGetTelFromRedis(t *testing.T) {
	mock := mocker.Create()
	mockConn := redis.NewConn(nil, time.Second, time.Second)
	mockErr := errors.New("mock error")
	tests := []struct {
		name    string
		prepare func()
		want    uint64
		wantErr bool
	}{
		{
			name: "dial redis error",
			prepare: func() {
				mock.Func(redis.Dial).Return(nil, mockErr)
			},
			want:    0,
			wantErr: true,
		},
		{
			name: "redis get nil",
			prepare: func() {
				mock.Struct(mockConn).Method("Do").Return("", redis.ErrNil)
				mock.Struct(mockConn).Method("Close").Return(nil)
				mock.Func(redis.Dial).Return(mockConn, nil)
			},
			want:    0,
			wantErr: true,
		},
		{
			name: "redis get error",
			prepare: func() {
				mock.Struct(mockConn).Method("Do").Return("", mockErr)
				mock.Struct(mockConn).Method("Close").Return(nil)
				mock.Func(redis.Dial).Return(mockConn, nil)
			},
			want:    0,
			wantErr: true,
		},
		{
			name: "redis get invalid string number",
			prepare: func() {
				mock.Struct(mockConn).Method("Do").Return("abc", nil)
				mock.Struct(mockConn).Method("Close").Return(nil)
				mock.Func(redis.Dial).Return(mockConn, nil)
			},
			want:    0,
			wantErr: false,
		},
		{
			name: "parse uint error",
			prepare: func() {
				mock.Struct(mockConn).Method("Do").Return("3.2", nil)
				mock.Struct(mockConn).Method("Close").Return(nil)
				mock.Func(redis.Dial).Return(mockConn, nil)
			},
			want:    0,
			wantErr: true,
		},
		{
			name: "success",
			prepare: func() {
				mock.Struct(mockConn).Method("Do").Return("32", nil)
				mock.Struct(mockConn).Method("Close").Return(nil)
				mock.Func(redis.Dial).Return(mockConn, nil)
			},
			want:    32,
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.prepare()
			defer mock.Reset()

			got, err := GetTelFromRedis("uid")
			if (err != nil) != tt.wantErr {
				t.Errorf("GetTelFromRedis() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("GetTelFromRedis() got = %v, want %v", got, tt.want)
			}
		})
	}
}

方案三

还有一种办法: 使用miniredis 模拟一个本地的Redis, 在测试方法init 时启动一个 miniredis 生成一个本地可用的IP,然后用redis.NewClientProxy 连接就行。 这样这个Redis就可以直接读写,

import "github.com/alicebob/miniredis"

//run mini Redis
s, err := miniredis.Run()
if err != nil {
   panic(err)
}

redisarr := "ip://" + s.Addr()
rediscliOK = redis.NewClientProxy("trpc.redis.server.service", client.WithTarget(redisarr))