LoginSignup
3
1

More than 3 years have passed since last update.

[Go言語] GORMを使った単体テストでのDBの初期化

Posted at

はじめに

GORMにはLaravelにあるような、DBのリフレッシュが用意されていません。
そのため、DBの単体テストをするためには自分でプログラムを準備する必要があります。

前提

まず単体テストをしたい関数ですが、以下のような全件取得するものサンプルとします。
DDDで作っているので、Structを使っていますがこの記事には関係ありません。

type userPersistence struct {
    db *gorm.DB
}

func NewUserPersistence(r Repository) repository.UserRepository {
    return &userPersistence{r.GetConn()}
}

func (bp bookPersistence) GetAllBook() ([]entity.Book, error) {
    var books []entity.Book
    err := bp.db.Find(&books).Error
    if err != nil {
        log.Error(err.Error())
        return nil, errors.New("Get all book failed")
    }
    return books, nil
}

単体テスト

事前準備

単体テスト本体の紹介の前に、準備用のプログラムです。
ポイントは3つあります。

  1. NewTestRepositoryで、DropTableAutoMigrateを使ってDB初期化してます。
  2. insertBooksでテストケース用のデータを投入できるようにしています。
  3. DBでミリ秒が落ちてしまうので、convertDBTimeで時間の精度を調整しています。
const location = "Asia/Tokyo"

// NewRepository DBへ接続を行う
func NewRepository(conf config.Config) (Repository, func(), error) {
    DBURL := fmt.Sprintf("%s:%s@(%s:%d)/%s?parseTime=true&loc=Asia%%2FTokyo", conf.DB.User, conf.DB.Password, conf.DB.Host, conf.DB.Port, conf.DB.Name)
    db, err := gorm.Open("mysql", DBURL)
    if err != nil {
        return nil, nil, err
    }

    // DB切断用の関数を定義
    cleanup := func() {
        if err := db.Close(); err != nil {
            log.Print(err)
        }
    }

    return &repositoryStruct{
        db: db,
    }, cleanup, nil
}

// Automigrate マイグレーション
func (r *repositoryStruct) Automigrate() error {
    return r.db.AutoMigrate(&entity.Book{}).Error
}

// Connectionを取得する
func (r *repositoryStruct) GetConn() *gorm.DB {
    return r.db
}


// NewTestRepository test用DBを準備する
func NewTestRepository() (Repository, func(), error) {
    // test用の設定ファイルを読み込む
    conf, err := config.NewConfig()
    if err != nil {
        return nil, nil, err
    }

    // test用DBへ接続する
    repo, cleanup, err := NewRepository(conf)
    if err != nil {
        return nil, nil, err
    }

    // cleanup db for test
    err = repo.GetConn().DropTableIfExists(&entity.Book{}).Error
    if err != nil {
        return nil, nil, err
    }

    // create tables for test
    err = repo.GetConn().AutoMigrate(
        entity.Book{},
    ).Error
    if err != nil {
        return nil, nil, err
    }
    return repo, cleanup, nil
}

func seedBooks() ([]entity.Book, error) {
    now, err := convertDBTime(now())
    if err != nil {
        return nil, err
    }
    books := []entity.Book{
        {
            Isbn:      "9784798121963",
            Title:     "エリック・エヴァンスのドメイン駆動設計",
            Author:    "エリック・エヴァンス",
            CreatedAt: *now,
            UpdatedAt: *now,
        },
        {
            Isbn:      "9784873117522",
            Title:     "Go言語によるWebアプリケーション開発",
            Author:    "Mat Ryer",
            CreatedAt: *now,
            UpdatedAt: *now,
        },
    }
    return books, nil
}

func insertBooks(r Repository) ([]entity.Book, error) {
    books, err := seedBooks()
    if err != nil {
        return nil, err
    }
    for _, v := range books {
        err = r.GetConn().Create(&v).Error
        if err != nil {
            return nil, err
        }
    }
    var insertedBooks []entity.Book
    err = r.GetConn().Find(&insertedBooks).Error
    if err != nil {
        return nil, err
    }
    return insertedBooks, nil
}

// now timezoneを指定した現在のtimeを返す
func now() time.Time {
    loc, err := time.LoadLocation(location)
    if err != nil {
        log.Fatal(err)
    }
    return time.Now().In(loc)
}

// convertDBTime ミリ秒を切り捨てる
func convertDBTime(t time.Time) (*time.Time, error) {
    loc, err := time.LoadLocation(location)
    if err != nil {
        log.Fatal(err)
    }
    const layout = "2006-01-02 15:04:05 -0700 MST"
    t, parseErr := time.Parse(layout, t.Format(layout))
    if parseErr != nil {
        return nil, parseErr
    }
    t = t.In(loc)
    return &t, nil

}

単体テスト本体

単体テストは以下のようになります。
テストケースごとにNewTestRepository()を呼び出すことで、DBをリフレッシュしています。
あとは普通の単体テストになります。

func TestGetAllBook_Success(t *testing.T) {
    // テスト用DBを準備
    testRepo, cleanup, err := NewTestRepository()
    if err != nil {
        log.Fatal("faild create test repository:", err)
    }
    defer cleanup()

    // テストデータをInsert
    seedBooks, err := insertBooks(testRepo)
    if err != nil {
        log.Fatal("faild insert data", err)
    }

    // テスト実行
    bp := NewBookPersistence(testRepo)
    books, getErr := bp.GetAllBook()
    if getErr != nil {
        log.Fatal(getErr)
    }

    // 結果の検証
    // Insetされているものが全て返ってきているか
    assert.ElementsMatch(t, seedBooks, books)
}

いかがだったでしょうか?
実際に使っているソースなので、関係ないソースが混じってしまっていますが、少しでも皆様の参考になれば幸いです!

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1