go-sqlmockを使ってGORMで書いたコードをテストする


gormを使ったコードのテストをするとき、Dockerなどを使ってDBを立ち上げてテストする必要がありますが、go-sqlmockを使うと、実際のDBの代わりにモックを使ってテストすることができます。

この記事は、簡単なモデルを例にしたサンプルコードです。

パッケージのインストール

gormとgo-sqlmockは以下のコマンドでインストールできます。

go get -u github.com/jinzhu/gorm
go get -u github.com/DATA-DOG/go-sqlmock

テスト対象のソース

RepositoryパターンでCreateとGetをGormで実装した例です。

user/user.go
package user

import (
    "github.com/jinzhu/gorm"
)

type User struct {
    ID   string `gorm:"primary_key"`
    Name string
}

type Repository struct {
    *gorm.DB
}

func (p *Repository) Create(id string, name string) error {
    person := &User{
        ID:   id,
        Name: name,
    }
    return p.DB.Create(person).Error
}

func (p *Repository) Get(id string) (*User, error) {
    var person User

    err := p.DB.Where("id = ?", id).Find(&person).Error
    return &person, err
}

テストコード

先程のコードの、CreateとGetをそれぞれテストしてみます。

まず、DBモックとGORMのオープンです。

user/user_test.go
package user

import (
    "regexp"
    "testing"

    sqlmock "github.com/DATA-DOG/go-sqlmock"
    "github.com/jinzhu/gorm"
)

func getDBMock() (*gorm.DB, sqlmock.Sqlmock, error) {
    db, mock, err := sqlmock.New()
    if err != nil {
        return nil, nil, err
    }

    gdb, err := gorm.Open("postgres", db)
    if err != nil {
        return nil, nil, err
    }
    return gdb, mock, nil
}

これを使ってテストを書いてみます。

Createのテスト

user/user_test.go
func TestCreate(t *testing.T) {
    db, mock, err := getDBMock()
    if err != nil {
        t.Fatal(err)
    }
    defer db.Close()
    db.LogMode(true)

    r := Repository{DB: db}

    id := "2222"
    name := "BBBB"

    // Mock設定
    mock.ExpectQuery(regexp.QuoteMeta(
        `INSERT INTO "users" ("id","name") VALUES ($1,$2)
         RETURNING "users"."id"`)).
        WithArgs(id, name).
        WillReturnRows(
            sqlmock.NewRows([]string{"id"}).AddRow(id))

    // 実行
    err = r.Create(id, name)
    if err != nil {
        t.Fatal(err)
    }
}

mock.ExpectQueryのところで、期待するSQLとパラメータと、実行結果のレコードを設定しています。
実際に実行した時に、この期待結果と異なる場合、以下のようなエラーが返ります。

--- FAIL: TestCreate (0.00s)
    user_test.go:48: Query 'INSERT INTO "users" ("id","name") VALUES ($1,$2) RETURNING "users"."id"', arguments do not match: argument 0 expected [string - 2222X] does not match actual [string - 2222]

Getのテスト

user/user_test.go
func TestGet(t *testing.T) {
    db, mock, err := getDBMock()
    if err != nil {
        t.Fatal(err)
    }
    defer db.Close()
    db.LogMode(true)

    r := Repository{DB: db}

    id := "1111"
    name := "AAAA"

    // Mock設定
    mock.ExpectQuery(regexp.QuoteMeta(
        `SELECT * FROM "users" WHERE (id = $1)`)).
        WithArgs(id).
        WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).
            AddRow(id, name))

    // 実行
    res, err := r.Get(id)
    if err != nil {
        t.Fatal(err)
    }

    if res.ID != id || res.Name != name {
        t.Errorf("取得結果不一致  %+v", res)
    }
}