👌

Go言語でテスト対象が呼び出すファンクションをmock化して、パラメーターをインメモリDBに登録して検証する

2024/09/17に公開

初めに

Go言語のテストコードで、テスト対象のファンクション内で呼び出すファンクションのパラメーターを検証する際に、そのパラメーターをインメモリDBに登録し、その内容を検証して、実際に期待通りの値で呼び出されたかを確認する方法を説明します。

仕様ライブラリ

テスト対象

内部でNewExample()によって注入されたUserRepositoryStore(name, address string, age int) errorを呼び出し、nameaddressageのユーザー情報を登録する処理です。
この処理で呼び出されるStore()がMockの対象です。

package example

type UserRepository interface {
	// Mock対象
	Store(name, address string, age int) error
}

type Example interface {
	RegisterUser(name, address string, age int) error
}

type example struct {
	userRepository UserRepository
}

func NewExample(userRepository UserRepository) Example {
	return &example{userRepository: userRepository}
}

// テスト対象
func (e *example) RegisterUser(name, address string, age int) error {
	return e.userRepository.Store(name, address, age)
}

テストコード

package example

import (
	"fmt"
	"testing"

	"github.com/google/go-cmp/cmp"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"

	sqle "github.com/dolthub/go-mysql-server"
	"github.com/dolthub/go-mysql-server/memory"
	"github.com/dolthub/go-mysql-server/server"
	"github.com/dolthub/go-mysql-server/sql"
	"github.com/dolthub/go-mysql-server/sql/types"
)

var (
	dbName       = "mydb"
	tableName    = "users"
	address      = "localhost"
	port         = 3306
	mysqlServer  *server.Server
	testMemoryDB *gorm.DB
)

// gorm usersテーブルモデル
type User struct {
	ID      int64 `gorm:"primaryKey;autoIncrement:false"`
	Name    string
	Address string
	Age     int32
}

type userRepositoryMock struct{}

func NewUserRepositoryMock() UserRepository {
	return &userRepositoryMock{}
}

func (r userRepositoryMock) Store(name, addr string, age int) error {
	// インメモリDBに引数を登録
	return testMemoryDB.Debug().Create(User{Name: name, Address: addr, Age: int32(age)}).Error
}

func TestMain(m *testing.M) {
	// インメモリDB起動
	startTestMemoryDB()

	// gormでインメモリDBに接続
	initGorm()

	m.Run()

	if err := mysqlServer.Close(); err != nil {
		panic(err)
	}
}

func Test(t *testing.T) {
	example := NewExample(NewUserRepositoryMock())

	// テスト対象呼び出し
	err := example.RegisterUser("テスト 太郎", "東京都千代田区", 20)

	if err != nil {
		t.Error(err)
	}

	// インメモリDBに登録された引数の内容を取得
	results := findUsers()

	// インメモリDBに登録された内容を検証
	expected := []User{{ID: 1, Name: "テスト 太郎", Address: "東京都千代田区", Age: 20}}
	if diff := cmp.Diff(expected, results, nil); diff != "" {
		t.Errorf("registered users is mismatch (-expected +actual):%s¥n", diff)
	}
}

func startTestMemoryDB() {
	db := memory.NewDatabase(dbName)
	db.BaseDatabase.EnablePrimaryKeyIndexes()
	pro := memory.NewDBProvider(db)

	// Store()呼び出し時のパラメーターを登録するテーブル作成
	table := memory.NewTable(db, tableName, sql.NewPrimaryKeySchema(sql.Schema{
		{Name: "id", Type: types.Int64, Nullable: false, Source: tableName, AutoIncrement: true, PrimaryKey: true},
		{Name: "name", Type: types.Text, Nullable: false, Source: tableName},
		{Name: "address", Type: types.Text, Nullable: false, Source: tableName},
		{Name: "age", Type: types.Int32, Nullable: false, Source: tableName},
	}), db.GetForeignKeyCollection())
	db.AddTable(tableName, table)

	engine := sqle.NewDefault(pro)

	mysqlConfig := server.Config{
		Protocol: "tcp",
		Address:  fmt.Sprintf("%s:%d", address, port),
	}

	s, err := server.NewServer(mysqlConfig, engine, memory.NewSessionBuilder(pro), nil)
	if err != nil {
		panic(err)
	}
	mysqlServer = s

	go s.Start()
}

func initGorm() {
	dsn := "root:@tcp(localhost:3306)/mydb?charset=utf8mb4&parseTime=True&loc=Local"
	gormDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	testMemoryDB = gormDB
}

func findUsers() []User {
	var results []User

	err := testMemoryDB.Debug().Find(&results).Error
	if err != nil {
		panic(err)
	}

	return results
}

テストの流れ

おおまかに、下記のような流れとなります。

  1. startTestMemoryDB()go-mysql-serverを使用して、インメモリDB起動
    1-1. DB作成
    1-2. id(PK, オートインクリメント), name, address, ageの4つのカラムをもつテーブル(users)を作成
    1-3. DBサーバー起動
  2. initGorm()で上記で作成したインメモリDBにGORMで接続
  3. テスト実行
    3-1. RegisterUser()が内部で呼び出すStore()を、インメモリDBのusersテーブルに登録するMock実装の内容で注入
    3-2. テスト対象のRegisterUser()呼び出し
    3-3. Store()は1でMockされたものが呼び出され、RegisterUser(name, address string, age)の3つの引数がそのままStore()呼び出し時のパラメーターに指定され、対応する値がusersテーブルの各カラムに登録されます。(idはオートインクリメントの値として定義しているので、登録時にUserモデルに指定しなければ自動で登録されます)
    3-4. インメモリDBに登録された引数の内容を取得(GORMでSELECT * FROM usersのクエリを発行)
    3-5. 3-3で取得した内容をgo-cmpで検証

最後に

Go言語のMockの検証だと、github.com/stretchr/testify/mockを使用していましたが、今回はインメモリDBを作成して、そこに呼び出し時のパラメータを登録し、その内容を検証する方法でテストコードを実装してみました。
テスト対象に応じて上手く使い分けて、保守性に優れ、価値あるテストコードが作れるようになればと思います。

レスキューナウテックブログ

Discussion