はじめに
外部サービスへのHTTPリクエスト処理をテストする場合、mockサーバーを使うことが多いと思います。
挙動確認目的で実際にサーバーに何度もリクエストを送ると迷惑をかけてしまいますからね。
そこでこの記事ではYahooのTokenエンドポイントを例に、Prismでmockサーバーを建ててGoのリクエストをテストしてみたいと思います。
実装
dockerでprismイメージからmockサーバーを構築して、そのmockサーバーにリクエストを送ります。
リクエスト時のパラメータや想定されるレスポンスは、Yahoo!デベロッパーネットワーク(Tokenエンドポイント)を参考にしています。
※以下のコードで実際にYahoo Tokenエンドポイントに正しくリクエストできるかは確認していないのであしからず。あくまでテストに焦点を当てた内容となっています。
まずはメインロジックとなるgoファイルから見ていきます。
package main
import (
"encoding/json"
"fmt"
"io"
"log"
)
func main() {
var tokenParams TokenParams
endpoint := "https://auth.login.yahoo.co.jp/yconnect/v2/token" //YahooのTokenエンドポイント
res, e := requestTokenEndpoint(endpoint)
if e != nil {
log.Fatalln(e)
}
body, e := io.ReadAll(res.Body)
if e != nil {
log.Fatalln(e)
}
e = json.Unmarshal([]byte(body), &tokenParams)
if e != nil {
log.Fatalln(e)
}
fmt.Printf("%+v\n", tokenParams)
}
package main
import (
"net/http"
"net/url"
"strings"
)
// レスポンスパラメータをマッピングするためのstruct
type TokenParams struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
IdToken string `json:"id_token"`
}
func requestTokenEndpoint(endpoint string) (res *http.Response, e error) {
// リクエストボディに含めるパラメータ
values := url.Values{}
values.Add("grant_type", "authorization_code")
values.Add("redirect_uri", "https://example.com")
values.Add("code", "SxlOBeZQ")
req, e := http.NewRequest(http.MethodPost, endpoint, strings.NewReader(values.Encode()))
if e != nil {
return nil, e
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Authorization", "Basic SlAV32hkKG")
client := &http.Client{}
res, e = client.Do(req)
if e != nil {
return nil, e
}
return res, nil
}
package main
import (
"encoding/json"
"fmt"
"io"
"testing"
)
func TestRequestTokenEndpoint(t *testing.T) {
var tokenParams TokenParams
url := "http://localhost:4010/yahoo-token" // ローカルのmockサーバーのエンドポイント
expectedAccessToken := "SlAV32hkKG" // mockサーバーから返ってくるaccess_token
t.Run("success test", func(t *testing.T) {
res, e := requestTokenEndpoint(url)
if e != nil {
t.Error(e)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
t.Errorf("mock server return status code: %s", res.Status)
}
body, _ := io.ReadAll(res.Body)
e = json.Unmarshal(body, &tokenParams)
if e != nil {
t.Error(e)
}
if expectedAccessToken != "SlAV32hkKG" {
t.Errorf("expected access_token is %s, but got %s", expectedAccessToken, tokenParams.AccessToken)
}
fmt.Printf("%+v\n", tokenParams)
})
}
次に構築するmockサーバーの定義を見ていきます。
openapi: "3.0.0"
info:
version: 1.0.0
title: prism sample
description: prism sample
servers:
- url: http://localhost:4010
description: local mock server
paths:
/yahoo-token:
post:
description: return token parameters
requestBody:
content:
"application/x-www-form-urlencoded":
schema:
type: object
required:
- grant_type
- redirect_uri
- code
properties:
grant_type:
type: string
redirect_uri:
type: string
code:
type: string
responses:
"200":
description: success
content:
"application/json":
schema:
type: object
properties:
access_token:
type: string
example: SlAV32hkKG
token_type:
type: string
example: Bearer
refresh_token:
type: string
example: 8xLOxBtZp8
expires_in:
type: integer
example: 3600
id_token:
type: string
example: eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6qJp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJNqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7TpdQyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoSK5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg
paths.post.requestBody
にリクエスト時に必要なパラメータを定義します。すべてのパラメータをrequiredに記述しているので、リクエスト時にパラメータが一つでも欠けていると422ステータスが返ります。
そして、paths.post.responses
にレスポンスとして返すパラメータを定義しています。
最後にdocker-comopseを見ていきます。
version: "3"
services:
swagger-server:
image: stoplight/prism:3
container_name: "swagger-server"
ports:
- "4010:4010"
command: mock -h 0.0.0.0 /openapi.yml
volumes:
- ./openapi.yml:/openapi.yml
prismイメージをpullして4010ポートで受け付けます。また、先ほど定義したopenapi.yml
をマウントしてmockコマンドを実行することで、定義に沿ったmockサーバーをローカルに構築します。
これでテストの準備ができました!
docker-compose up
してmockサーバーを立ち上げてみましょう。
$ docker-compose up
swagger-server | [2:54:03 PM] › [CLI] … awaiting Starting Prism…
swagger-server | [2:54:07 PM] › [CLI] ℹ info POST http://0.0.0.0:4010/yahoo-token
swagger-server | [2:54:07 PM] › [CLI] ▶ start Prism is listening on http://0.0.0.0:4010
mockサーバーが立ち上がったので、go test
でリクエストを送ってみます。
$ go test -v
=== RUN TestRequestTokenEndpoint
=== RUN TestRequestTokenEndpoint/success_test
{AccessToken:SlAV32hkKG TokenType:Bearer RefreshToken:8xLOxBtZp8 ExpiresIn:3600 IdToken:eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6qJp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJNqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7TpdQyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoSK5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg}
--- PASS: TestRequestTokenEndpoint (0.16s)
--- PASS: TestRequestTokenEndpoint/success_test (0.16s)
PASS
ok github.com/pae26/go-prism 0.482s
無事テストが通って、openapi.yml
で定義したレスポンスパラメータ(access_token
やtoken_type
など)が返ってきてることが確認できました!
試しにリクエストパラメータを減らしてみます。
...
func requestTokenEndpoint(endpoint string) (res *http.Response, e error) {
values := url.Values{}
// values.Add("grant_type", "authorization_code")
values.Add("redirect_uri", "https://example.com")
values.Add("code", "SxlOBeZQ")
...
$ go test -v
=== RUN TestRequestTokenEndpoint
=== RUN TestRequestTokenEndpoint/success_test
request_token_endpoint_test.go:24: mock server return status code: 422 Unprocessable Entity
request_token_endpoint_test.go:33: expected access_token is SlAV32hkKG, but got
{AccessToken: TokenType: RefreshToken: ExpiresIn:0 IdToken:}
--- FAIL: TestRequestTokenEndpoint (0.03s)
--- FAIL: TestRequestTokenEndpoint/success_test (0.03s)
FAIL
exit status 1
FAIL github.com/pae26/go-prism 0.353s
openapi.yml
のrequiredに定義したパラメータが足りていないので、422 Unprocessable Entity
が返っていることがわかります。
おわりに
mockサーバーを定義・構築し、Goのリクエストをテストする流れを見てきました。
Goをクライアントとして外部サーバーにリクエストする処理(今回扱ったソーシャルログインなど)を実装する際の参考になればいいなと思います。