はじめに
こんにちは、STORESの高田です。
STORES には Go で実装した GraphQL API サーバがあり、そのプロジェクトでは Go のテスト内で E2E テストを行っています。今回はそのテスト方法についてご紹介します。
実装例
今回の E2E テストは CI でも実行したいため、再現性のある安定したテストであることが求められます。
少し工夫する必要がありますが、テスト対象となるサーバのポート確保時に、エニーポートを指定して動的にポートを割り当てることで安定したテストを実現できます。
以下のコードのように、 TestMain
内での net.Listen("tcp", "localhost:0")
にて動的にポートを割り当てます。
var client AppGraphQLClient func TestMain(m *testing.M) { // データベースなどの初期化 ... ln, err := net.Listen("tcp", "localhost:0") // 動的にポートを割り当てる if err != nil { log.Fatal(err) } addrPort, err := netip.ParseAddrPort(ln.Addr().String()) if err != nil { log.Fatal(err) } httpCli := &http.Client{} client = NewClient(httpCli, fmt.Sprintf("http://localhost:%d/graphql", addrPort.Port()), nil) srv := &http.Server{ Handler: di.InitializeHandler(), ReadHeaderTimeout: 10 * time.Second, } ch := make(chan struct{}) go func() { if err := srv.Serve(ln); err != http.ErrServerClosed { log.Fatal(err) } close(ch) // データベースなどの teardown ... }() m.Run() if err := srv.Shutdown(context.Background()); err != nil { log.Fatal(err) } <-ch }
API クライアントの生成には github.com/Yamashou/gqlgenc
を使用しており、client
変数経由で query や mutation を呼び出してテストを実施します。E2E テストは以下のテストコードのように実装しています。
func TestE2EMe(t *testing.T) { // データベースなどの初期化 ... want := &Me{ Me: Me_Me{ UserID: "10000000-0000-0000-0000-000000000000", }, } got, err := client.Me(context.Background(), func(ctx context.Context, req *http.Request, gqlInfo *clientv2.GQLRequestInfo, res interface{}, next clientv2.RequestInterceptorFunc) error { req.Header.Set("X-User-Id", "10000000-0000-0000-0000-000000000000") req.Header.Set("X-Scopes", "openid email offline_access") return next(ctx, req, gqlInfo, res) }) if err != nil { t.Fatal(err) } if diff := cmp.Diff(want, got); diff != "" { t.Errorf("diff (-want +got):\n%s", diff) } }
今回はテストの実装方針として TestMain
で初期化する方針にしていますが、初期化処理は局所化することも可能なので、プロジェクトにとって都合の良い方針で進められると良いと思います。また、RESTful API の場合にも応用可能です。事例として参考にしてもらえればと思います.