wheatandcatの開発ブログ

React Nativeで開発しているペペロミア & memoirの技術系記事を投稿してます

GoMockを使ってHTTP Requestのテストを書いてみる

backend側のコードも増えてきたのでテストコードを追加しました。 backendのテストをする時はfirestoreに接続したくなかったので、回避するためにGoMockを使って実装してみました。

github.com

GoMockはテスト用のモックを実装することで、テスト時に呼びたくない処理をモックで実装させることが可能になります。

やれることは、jest.mockに近いですがjsほどダイナミックな処理は書けないので色々と事前準備が必要なので、その書き方について紹介していきます。

jestjs.io

まず、以下のコマンドを実行

$ go get github.com/golang/mock/gomock
$ go install github.com/golang/mock/mockgen

今回は POST /CreateItem のテストコードを書いていくので、その中でfirestoreを使用している repository/item.go内の処理をMockしていきます。

■ 修正前の「repository/item.go」

Peperomia/item.go at b8497495889324dfd8b66755108c32b9bfb50c2b · wheatandcat/Peperomia · GitHub

まず、GoMockをするのはinterfaceに対してMockする感じになるのでdomainのレイヤーに以下を追加します。

■ domain/item.go

// ItemRepository is repository interface
type ItemRepository interface {
    Create(ctx context.Context, f *firestore.Client, i ItemRecord) error
    Update(ctx context.Context, f *firestore.Client, i ItemRecord) error
    Delete(ctx context.Context, f *firestore.Client, i ItemRecord) error
    FindByUID(ctx context.Context, f *firestore.Client, uid string) ([]ItemRecord, error)
    DeleteByUID(ctx context.Context, f *firestore.Client, uid string) error
}

そして、repository/item.go側に、↑で定義したinterfaceを設定します。

■ repository/item.go

// NewItemRepository is Create new ItemRepository
func NewItemRepository() domain.ItemRepository {
  return &ItemRepository{}
}

これで、ItemRepositoryのMockを作成する準備が整ったので、次に以下のコマンドでMockファイルを生成します。

$ mockgen -source domain/item.go -destination domain/mocks/item.go

このコマンドで、↑で定義したinterfaceを元に以下のファイルが生成されます。

https://github.com/wheatandcat/Peperomia/blob/master/backend/domain/mocks/item.go

そして以下のようにテストコードに組み込んでいきます。

■ handler/item_test.go

func TestCreate(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()
    ctx := context.Background()

    mock := mock_domain.NewMockItemRepository(ctrl)
    i := domain.ItemRecord{
        ID:    "sample-uuid-string",
        UID:   "test",
        Title: "test",
        Kind:  "test",
    }

    mock.EXPECT().Create(gomock.Any(), gomock.Any(), i).Return(nil)

    app := &handler.Application{
        ItemRepository: mock,
    }

    h := NewTestHandler(ctx, app)

以下で生成したMockで実行したい処理を

 mock := mock_domain.NewMockItemRepository(ctrl)

以下で、呼ばれるメソッドの引数と戻り値を事前に設定しておく。(※ gomock.Any()にすると引数は何でもOKになります)

 mock.EXPECT().Create(gomock.Any(), gomock.Any(), i).Return(nil)

そしてMockした定義をhandlerのFieldに設定します。

 app := &handler.Application{
        ItemRepository: mock,
    }

    h := NewTestHandler(ctx, app)

そして、handlerのFieldに設定して値を実装の方でも使用することで、実行時はMockしていない処理、テスト時はMockした処理の切り分けを行っています。

■ handler/item.go

// CreateItem 予定を作成する
func (h *Handler) CreateItem(gc *gin.Context) {
    (略)

    if err := h.App.ItemRepository.Create(ctx, h.FirestoreClient, item); err != nil {
        NewErrorResponse(err).Render(gc)
        return
    }

その他にも、UUID周りのテストでもハマりましたが、そちらはUUIDの部分を参考に対応しました。

devblog.thebase.in

最終的には、こんな感じの修正で無事testが通るようになりました

■ 対象pull Request github.com

go testの結果

$ go test -v
=== RUN   TestCreate
=== RUN   TestCreate/ok
--- PASS: TestCreate (0.00s)
    --- PASS: TestCreate/ok (0.00s)
PASS
ok      github.com/wheatandcat/Peperomia/backend/handler    0.439s

これでやっとGo側のテストを書いていけそう