redigoのPoolとredigomockを利用する方法


redigoのredis.Poolはredis.Connとは異なりインタフェースではなく構造体として定義されています.

それとredigomockはredis.Poolを生成する機能を有していないことからぱっと見使うことができないだろうなっと判断しましたが

よくよく考えてみるとredis.Poolで利用する関数群をインタフェースとして定義すれば良いのではないかと気づきがあったので共有します.

以下Example

// service.go

package service

import (...) // 省略

type RedigoPool interface {
	Get() redis.Conn
	Close() error
}

type ExampleService struct {
	pool RedigoPool // *redis.Poolを直接定義せず, RedigoPoolというインタフェースを利用する
}

func (s *ExampleService) HmSetWithExpire(key string, a, b int64, expireSeconds uint) (interface{}, error) {
	redisConn := s.pool.Get()
	defer redisConn.Close()

	redisConn.Send("MULTI")
	redisConn.Send("HMSET", key, "a", a, "b", b)
	redisConn.Send("EXPIRE", key, expireSeconds)

	reply, err := redisConn.Do("EXEC")
	if err != nil {
		return nil, err
	}
	return reply, nil
}
// service_test.go

package service

import (...) // 省略

// redigoMockPool redigomock用RedisPool
type redigoMockPool struct {
	conn redis.Conn
}

func (m redigoMockPool) Get() redis.Conn {
	return m.conn
}

func (m redigoMockPool) Close() error {
	return m.conn.Close()
}

func TestExampleService_HmSetWithExpire(t *testing.T) {
	mockConn := redigomock.NewConn()
	mockPool := &redigoMockPool{
		conn: mockConn,
	}

	svc := ExampleService{
		pool: mockPool,
	}

	key := "test-uhyouhyo!"
	a := int64(1)
	b := int64(2)
	expireSeconds := uint(3200)

	cmd1 := mockConn.Command("MULTI")
	cmd2 := mockConn.Command("HMSET", key, "a", a, "b", b)
	cmd3 := mockConn.Command("EXPIRE", key, expireSeconds)
	cmd4 := mockConn.Command("EXEC").Expect([]interface{}{"OK", "OK"})

	_, err := svc.HmSetWithExpire(key, a, b, expireSeconds)
	if err != nil {
		t.Error(err)
	}
	
	if counter := mockConn.Stats(cmd1); counter != 1 {
		t.Errorf("Expected cmd1 to be called once but was called %d times", counter)
	}

	if counter := mockConn.Stats(cmd2); counter != 1 {
		t.Errorf("Expected cmd2 to be called once but was called %d times", counter)
	}

	if counter := mockConn.Stats(cmd3); counter != 1 {
		t.Errorf("Expected cmd3 to be called once but was called %d times", counter)
	}

	if counter := mockConn.Stats(cmd4); counter != 1 {
		t.Errorf("Expected cmd4 to be called once but was called %d times", counter)
	}
}

上記のコードのRedigoPoolが味噌で予めredis.Poolで利用する関数をインタフェースに定義しておけば, 割とさくっとMockを利用可能になります.

ちなみにredis.Pool#Dialredigomock.Connを取得できるようにすればいいのでは? っとおもう方もいると思いますが,

redis.Connを取得するredis.Pool#Getは複雑なことをやってるのでうまいことredigomock.Connを取得することができません.

Ref