regexpパッケージが便利だった

はじめに

クエリのプロセスをモックするgo-sqlmockというライブラリがあったので使ってみたところメタ文字のエスケープが必要になったので便利なものがないか探してみた

実際に使ってみた

repository/account_repository.go

package repository

import (
    "database/sql"

    "github.com/sample/sqlmock/domain"
)

// AccountRepository repository interface
type AccountRepository interface {
    Update(*domain.Account) error
}

type accountRepository struct {
    Conn *sql.DB
}

// NewAccountRepository new accountrepository
func NewAccountRepository(db *sql.DB) AccountRepository {
    return &accountRepository{
        Conn: db,
    }
}

// Update update Account
func (m *accountRepository) Update(u *domain.Account) error {
    query := `UPDATE accounts SET password = $1, email = $2, updated_at = now() WHERE id = $3`
    stmt, err := m.Conn.Prepare(query)
    if err != nil {
        return err
    }

    _, err = stmt.Exec(u.Password, u.Email, u.ID)
    if err != nil {
        return err
    }
    return nil
}

repository/account_repository_test.go

package repository_test

import (
    "fmt"
    "regexp"
    "testing"

    "github.com/sample/sqlmock/domain"
    "github.com/sample/sqlmock/repository"
    uuid "github.com/satori/go.uuid"
    "github.com/stretchr/testify/assert"
    sqlmock "gopkg.in/DATA-DOG/go-sqlmock.v1"
)

func TestUpdate(t *testing.T) {
    u := &domain.Account{
        ID:       uuid.NewV4(),
        Email:    "example@example.com",
        Password: "asdf0987",
    }

    db, mock, err := sqlmock.New()
    if err != nil {
        t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
    }
    defer db.Close()

    query := "UPDATE accounts SET password = $1, email = $2, updated_at = now() WHERE id = $3"
    prep := mock.ExpectPrepare(query)
    prep.ExpectExec().WithArgs(u.Password, u.Email, u.ID).WillReturnResult(sqlmock.NewResult(1, 1))

    a := repository.NewAccountRepository(db)
    err = a.Update(u)
    assert.NoError(t, err)
    return
}

詰まったところ

テストのmock.ExpectPrepare()のときにqueryの文字列をそのまま渡すとエラーになってテストが失敗していた

$ go test -v
=== RUN   TestUpdate
--- FAIL: TestUpdate (0.00s)
        Error Trace:      account_repository_test.go:32
        Error:            Received unexpected error:
                          Prepare query string 'UPDATE accounts SET password = $1, email = $2, updated_at = now() WHERE id = $3', does not match regex [UPDATE accounts SET password = $1, email = $2, updated_at = now() WHERE id = $3]
        Test:             TestUpdate
FAIL
exit status 1
FAIL    github.com/sample/sqlmock/repository    0.020s

go-sqlmockのissueに上がっていたので見てみた どうやらメタ文字をエスケープしないとエラーが返ってくるみたいだ

つまりaccount_repository_test.goのqueryをこうすればテストが成功する

query := "UPDATE accounts SET password = \$1, email = \$2, updated_at = now\(\) WHERE id = \$3"

しかし、毎回エスケープするのも面倒なのでできれば関数か何かを通してエスケープできるようにしたい

regexpのQuoteMeta

QuoteMetaは、引数text内のすべての正規表現メタ文字を引用符で囲む文字列を返す。 リテラルテキストに一致する正規表現が返ってくる。 例えば、QuoteMeta( \[foo\])は \ \[foo \\\]を返す。

これをaccount_repository_test.goに書くとこうなる

query := "UPDATE accounts SET password = $1, email = $2, updated_at = now() WHERE id = $3"
prep := mock.ExpectPrepare(regexp.QuoteMeta(query))

これでテストが通るようになった

所感

regexpパッケージは便利

だけど処理速度が遅いのでアプリ自体にQuoteMetaとかは使わないほうがいいのかなと

テストに使う分には十分便利