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 を使えば ? などの記号をエスケープできる
今のところ特に恩恵を感じないというか、このテストを書いた意味を感じられていない。
もっと複雑な関数になると書いた意味が出てくるかもしれない。