Tags: Go Golang

go-sqlmock でテストをとりあえず書く

go-sqlmock でテストをとりあえず書く。

開発環境

  • go version go1.17.5 linux/amd64

本文

下記のモックライブラリを使って DB まわりの単体テストを書く。

https://github.com/DATA-DOG/go-sqlmock

詳しくは godoc を参照。
だが結構わかりづらかったので下記も若干参考にした。

SELECT, INSERT のテストをとりあえず書いてみた。

テストされる側のソースコード

package user

import (
	"database/sql"
	"strconv"
	"time"

	"github.com/pkg/errors"

	"github.com/k1350/sololog/internal/domain/model/user"
	iu "github.com/k1350/sololog/internal/domain/repository/user"
	ae "github.com/k1350/sololog/internal/errors"
)

type UserRepository struct {
	*sql.DB
}

func NewUserRepository(db *sql.DB) iu.IUserRepository {
	return &UserRepository{db}
}

func (r *UserRepository) IsExist(uid string) (bool, error) {
	cSqlString := `
		SELECT 
		 count(1)
		FROM users 
		WHERE uid = ? `

	var i string
	if err := r.QueryRow(cSqlString, uid).Scan(&i); err != nil {
		err = errors.Wrap(ae.DbError, err.Error())
		return false, err
	}
	count, err := strconv.Atoi(i)
	if err != nil {
		err = errors.Wrap(ae.DbError, err.Error())
		return false, err
	}
	return count > 0, nil
}

func (r *UserRepository) Create(user *user.User) error {
	iSqlString := `
		INSERT INTO users
		(uid, last_logined, created_at, updated_at)
		VALUES
		(?, ?, ?, ?)
		`

	_, err := r.Exec(iSqlString, user.Uid, user.LastLogined, user.CreatedAt, user.UpdatedAt)
	if err != nil {
		err = errors.Wrap(ae.DbError, err.Error())
		return err
	}
	return nil
}

テストコード

package user

import (
	"fmt"
	"regexp"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"

	"github.com/k1350/sololog/internal/domain/model/user"
)

func TestIsExist(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		fmt.Println("failed to open sqlmock database:", err)
	}
	defer db.Close()

	rows := sqlmock.NewRows([]string{"count"}).
		AddRow(1)

	mock.ExpectQuery(regexp.QuoteMeta("SELECT count(1) FROM users WHERE uid = ?")).
		WithArgs("one").
		WillReturnRows(rows)

	repo := NewUserRepository(db)
	_, err = repo.IsExist("one")

	if err != nil {
		t.Error("got err:", err)
	}

	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("there were unfulfilled expectations: %s", err)
	}
}

func TestCreate(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		fmt.Println("failed to open sqlmock database:", err)
	}
	defer db.Close()

	now := time.Now()

	mock.ExpectExec(regexp.QuoteMeta("INSERT INTO users (uid, last_logined, created_at, updated_at) VALUES (?, ?, ?, ?)")).
		WithArgs("one", now, now, now).
		WillReturnResult(sqlmock.NewResult(1, 1))

	u := user.NewUser("one", now, now, now)

	repo := NewUserRepository(db)
	err = repo.Create(u)

	if err != nil {
		t.Error("got err:", err)
	}

	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("there were unfulfilled expectations: %s", err)
	}
}

とりあえず書いてみるときのポイント

  • SELECT の場合は ExpectQuery, WillReturnRows を使う
  • SELECT 以外は ExpectExec, WillReturnResult を使う
  • ExpectQuery, ExpectExec に指定するのは正規表現。全文そのまま書きたい場合、regexp.QuoteMeta を使えば ? などの記号をエスケープできる

今のところ特に恩恵を感じないというか、このテストを書いた意味を感じられていない。
もっと複雑な関数になると書いた意味が出てくるかもしれない。