週刊人生詰んだマガジン

フリーランスフロントエンドエンジニアTinyKittenのブログ

LabStack EchoでJWTトークンが必要なハンドラーに対してのテストでハマった話

f:id:tinykitten:20171115075850j:plain

みなさんGo使ってますか。使ってようが使ってなかろうがどうでもいいのですが、

TimelineServerの開発にはGoとWebフレームワークのLabStack Echo使ってます。

で、皆さんJWT好きですか。あなた達が好きかどうかはどうでもいいのですが、TimelineServerの認可にはJWTトークンを使ってます。EchoのMiddlewareが用意されてて便利です。

 

閑話休題

そのJWTミドルウェアを使ったハンドラをやっぱりテストしたいわけです。で、どうインジェクションしてテストするか調べてもなかなか出てこなかったので、ここにメモっときます。

まずハンドラのコードです:

func (h *handler) getPublicPostsHandler(c echo.Context) error {
    user := c.Get("user").(*jwt.Token)
    claims := user.Claims.(jwt.MapClaims)
    objID := claims["id"].(string)

    if h.checkSuspended(objID) {
        return &echo.HTTPError{Code: http.StatusForbidden, Message: ErrSuspended}
    }

    limit, _ := strconv.Atoi(c.QueryParam("limit"))

    posts, err := h.db.GetAllPosts(limit)
    if err != nil {
        return &echo.HTTPError{Code: http.StatusInternalServerError, Message: ErrUnknown}
    }

    return c.JSON(http.StatusOK, posts)
}

ウンコ並みに汚いですね。

コイツをテストするとなるとMongoDBのモックを作ったり(これについては後の記事で)、まぁJWTのインジェクションが必要になるわけですね。

で、コイツをどうするかっつーと

func TestGetPublicPostsHandler(t *testing.T) {
    e := echo.New()
    req := httptest.NewRequest(echo.GET, "/v1/posts", nil)
    u := models.NewUser("id", "password", "mail@example.com")
    err := th.db.Create("users", u)
    if err != nil {
        t.Errorf(err.Error())
    }
    token, err := token.CreateToken(u.ID)
    if err != nil {
        t.Errorf(err.Error())
    }
    req.Header.Set(echo.HeaderAuthorization, fmt.Sprintf("Bearer %v", token))
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)
    exec := middleware.JWTWithConfig(middleware.JWTConfig{
        SigningKey: byte(config.MockJwtToken),
    })(th.getPublicPostsHandler)(c)

    if assert.NoError(t, exec) {
        assert.Equal(t, http.StatusOK, rec.Code)
        assert.Equal(t, "null", rec.Body.String())
    }
}

すげぇどうでもいいコードが混じってますが、よしなにトークンを作ってヘッダーにセットしてmiddleware.JWTWithConfig経由でハンドラを実行してます。

重要なのは、

    token, err := token.CreateToken(u.ID)
    if err != nil {
        t.Errorf(err.Error())
    }
    req.Header.Set(echo.HeaderAuthorization, fmt.Sprintf("Bearer %v", token))
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)
    exec := middleware.JWTWithConfig(middleware.JWTConfig{
        SigningKey: byte(config.MockJwtToken),
    })(th.getPublicPostsHandler)(c)

 

    if assert.NoError(t, exec) {
        assert.Equal(t, http.StatusOK, rec.Code)
        assert.Equal(t, "null", rec.Body.String())
    }

あたりかな。

f:id:tinykitten:20171115081111p:plain

やったぜ。