slack-go/slackを使ったAPI通信をモックしてテストする
Go言語でSlack APIを使ったシステムを作る場合https://pkg.go.dev/github.com/slack-go/slackが便利なのですが、テストどう書くと良いかな〜と迷いました。
先に結論
結論から言うとhttps://pkg.go.dev/github.com/slack-go/slack/slacktestパッケージを利用するとこのように記述することができます。
// ① //go:embed testdata/sample.json var sampleJSON []byte func Test(t *testing.T) { // ② ts := slacktest.NewTestServer(func(c slacktest.Customize) { c.Handle("/conversations.history", func(w http.ResponseWriter, r *http.Request) { w.Write(sampleJSON) }) }) ts.Start() // ③ s := slack.New("testToken", slack.OptionAPIURL(ts.GetAPIURL())) resp, err := s.GetConversationHistory(nil) // 省略...
以下3つのポイントを説明します。
① Slack APIのレスポンスを模したJSONファイルを読み込む
Slackのテストには直接関係ありませんが、Slack APIのレスポンスを模したJSONファイルを用意して、Go1.16から使えるgo:embedで読み込みます。
② slacktestパッケージのNewTestServer関数を使ってモックサーバーを立ち上げる
slacktestパッケージをうまく使うことでラクにテストが書けました。
NewTestServer関数の実装は以下のようになっています。ポイントはbinder型の可変長引数を、for rangeでループしながら実行しているところです。またその下には固定でいくつかのAPIをモックしているのが見えますね。今回はここに無い/conversations.history
APIをモックします。
func NewTestServer(custom ...binder) *Server { serverChans := newMessageChannels() channels := &serverChannels{} groups := &serverGroups{} s := &Server{ registered: map[string]struct{}{}, mux: http.NewServeMux(), seenInboundMessages: &messageCollection{}, seenOutboundMessages: &messageCollection{}, } for _, c := range custom { c(s) } s.Handle("/conversations.info", s.conversationsInfoHandler) s.Handle("/ws", s.wsHandler) s.Handle("/rtm.start", rtmStartHandler) s.Handle("/rtm.connect", RTMConnectHandler) s.Handle("/chat.postMessage", s.postMessageHandler) s.Handle("/conversations.create", createConversationHandler) // 省略
binder型は何なのか見にいくと、Customizeインタフェースを引数に持つ関数だということが分かります。
// Customize the server's responses. type Customize interface { Handle(pattern string, handler http.HandlerFunc) } type binder func(Customize)
なので、モックしたいパスと、そのレスポンスをbinder型に合うようにNewTestServer関数に引数として渡してあげることで、モックサーバーが作れます。
ts := slacktest.NewTestServer(func(c slacktest.Customize) { c.Handle("/conversations.history", func(w http.ResponseWriter, r *http.Request) { w.Write(sampleJSON) }) }) ts.Start()
③ モックサーバーのURLを使ってslack.Clientを作成する
slackパッケージのNew関数でslack.Clientを作成する際に、引数にオプションを指定します。
slack.New関数は以下のような実装になっています。options引数はOptionの可変長引数で、for-rangeで実行していることから何かの関数だということがわかります。
// New builds a slack client from the provided token and options. func New(token string, options ...Option) *Client { s := &Client{ token: token, endpoint: APIURL, httpclient: &http.Client{}, log: log.New(os.Stderr, "slack-go/slack", log.LstdFlags|log.Lshortfile), } for _, opt := range options { opt(s) } return s }
Optionは以下のように定義されており、Clientのポインタを引数に持つ関数です。その下のコードを見ていくと、Optionを返すユースケースごとの関数がいくつか用意されています。今回は指定したURLのレスポンスをモックしたいので、OptionAPIURL関数を利用します。
// Option defines an option for a Client type Option func(*Client) // OptionHTTPClient - provide a custom http client to the slack client. func OptionHTTPClient(client httpClient) func(*Client) { return func(c *Client) { c.httpclient = client } } // OptionDebug enable debugging for the client func OptionDebug(b bool) func(*Client) { return func(c *Client) { c.debug = b } } // OptionLog set logging for client. func OptionLog(l logger) func(*Client) { return func(c *Client) { c.log = internalLog{logger: l} } } // OptionAPIURL set the url for the client. only useful for testing. func OptionAPIURL(u string) func(*Client) { return func(c *Client) { c.endpoint = u } }
モックサーバーのURLはts.GetAPIURL()で取得できるので、OptionAPIURL関数の引数に渡してあげます。
s := slack.New("testToken", slack.OptionAPIURL(ts.GetAPIURL()))
あとは、モックサーバーで指定したパスに該当するslack.Clientのメソッドを呼ぶことで、モックされたレスポンスを受け取ることができます。
s.GetConversationHistory(params) //paramsは別途定義が必要です