golang の単体テストを勉強していて、外部リソースへのアクセスをモック化する方法を調べていたら gomock というのがあることを知り、試したサンプルです。
環境準備
├── go.mod
├── model
│ └── book.go
├── repository
│ └── book_repository.go
└── service
└── book_service.go
サンプルは以下のような構成にしたいと思います。
- book_service.go -> book 関連のビジネスロジックを提供。
- book_repository.go -> book 関連の DB アクセス周りの処理を提供。
- book.go -> book 構造体定義
単体テスト対象のコード
続いてテスト対象コードを用意していきます。
book.go
book_repository.go
Repository にDB アクセス処理があると仮定して、これを後ほどモックします。
book_service.go
こちらの Service から、Repository の処理を呼び出します。
単体テストコード
mock の生成
まずは repository の mock を作成します。
gomock では、リンク先の README に書かれているように、mockgen コマンドで mock を自動生成できるようです。
それでは、先に作成した Repository の mock を作成してみます。
repository フォルダに移動して、下記のコマンドを実行します。
mockgen -source book_repository.go -destination mock/book_repository.go
すると、mock フォルダが生成され、その中に book_repository.go が生成されます。
生成された book_repository.go の下記のimport 句でコンパイルエラーになっているので、
unittest フォルダ直下で下記コマンドを実行して gomock の依存を追加します。
go mod tidy
単体テストコード
repository の mock が作れたので、続いて単体テストコードを書きます。
repository を使用する Service の単体テストを書いていきます。
テストの検証を簡潔にするため、github.com/stretchr/testify/assert を使用しています。
コンパイルエラーになると思うので、
go mod tidy
で assert の依存関係を追加します。
テストコードの大まかな実装の参考は gomockリファレンス より。
先に生成した mock を、mock_db としてインポートしています。
mockObj.EXPECT().{テスト対象関数}.RETURN({戻り値})
上記の形式で戻り値を指定できます。
その後、上記で設定した mock を、Service のパラメーターとして下記のように渡し、Service のメソッドを呼び出してテストを実行します。
go test
PASS
ok unittest/service 0.004s
テストがPASSしました。
ちゃんと設定した内容が mock に反映されているか確認するため、わざと失敗のテストケースにしてみます。
go test
--- FAIL: TestGetBookById (0.00s)
book_service.go:13: Unexpected call to *mock_repository.MockBookRepository.GetBookById([200]) at /root/repos/go-samples/unittest/service/book_service.go:13 because:
expected call at /root/repos/go-samples/unittest/service/book_service_test.go:19 doesn't match the argument at index 0.
Got: 200 (int)
Want: is equal to 100 (int)
controller.go:269: missing call(s) to *mock_repository.MockBookRepository.GetBookById(is equal to 100 (int)) /root/repos/go-samples/unittest/service/book_service_test.go:19
controller.go:269: aborting test due to missing call(s)
FAIL
exit status 1
FAIL unittest/service 0.005s
テストが失敗することが確認できました。
以上サンプルになります。