RspecでJSONオブジェクトの値を確認する

こんにちは、皆さん!

Railsウェブアプリケーションを作っていく上で、その品質を保証するためにRspecは欠かせない。そして、最近ではRailsAPIを作る人たちも増えてきている。そんなAPIなのだが、普通コントローラーがJSONオブジェクトという形でデータを返してくる。今回はそのオブジェクト内の値をチェックする方法を紹介したいと思う。


  • トピック:
  1. 今回使うモデルの説明
  2. うまくいかない例
  3. うまくいく例


今回使うモデルの説明

今回はUserモデルを使い、メール、パスワード、確認パスワードの3つを使ってユーザーを作成(つまり、POSTリクエスト)、そして、そのアクションで作られたユーザーをJSONオブジェクトとして返すという形をとる。下記がUserコントローラーのcreateアクション。

    # users_controller.rb
    def create
        @user = User.create(user_params)
         render json: @user, status: :created
    end

    private

    def user_params
        params.require(:user).permit(:email, :password, :password_confirmation)
    end

ベースのテストコードは下記のようになっている。今回はここにもう一つコードを足して、JSONとして返ってきたオブジェクトのメールがもともと入れたメールと同じか確認する。

    # users_controller_spec.rb
    describe "POST create" do
        it "should create a user with valid params" do
            post :create, params: {user: {email: "example@example.com", password: "foobar", password_confirmation: "foobar"}}
            expect(response).to have_http_status(:created)
        end
    end

覚えておくべきことは

  • ここで使うのはcreateアクションだけであること
  • そのアクションではJSONオブジェクトのUserが返され、:createdというステータスがついてくること
  • そのステータスはテストコードのPOSTリクエスト後に確認がされていること
  • response.bodyに返されたJSONオブジェクトの詳細が入っていること

では、実際に両方の例を見ていこう!!

うまくいかない例

まずよくやっちゃううまくいかない例。僕もこれをよくやりがちなのだが、JSONオブジェクトだからといって下記のようにそのままキーを使って値を取り出そうとするパターン。

    describe "POST create" do
        it "should create a user with valid params" do
            post :create, params: {user: {email: "example@example.com", password: "foobar", password_confirmation: "foobar"}}
            expect(response).to have_http_status(:created)
            expect(response.body['email']).to eq("example@example.com")
        end
    end

これはいつもJavaScriptでキーを使って値を抜き出してる人からしたら、とても常識的なやり方。しかし、Rubyでは下記のように想定外のことがおこり、テストが下記のようにフェイルしてしまう。

  1) V1::UsersController POST create should create a user with valid params
     Failure/Error: expect(response.body['email']).to eq("example@example.com")

       expected: "example@example.com"
            got: "email"

       (compared using ==)
     # ./spec/controllers/users_controller_spec.rb:16:in `block (3 levels) in <top (required)>'

これはつまり何かというと、response.body['email']の結果がemailになっているのだ。
試しにputs response.bodyresponse.bodyの内容をアウトプットしてみた。そしたら下記の結果が得られた。

{"id":841425152,"email":"example@example.com","created_at":"2018-12-30T19:26:45.289Z","updated_at":"2018-12-30T19:26:45.289Z"}

これって当たり前だけど、JavaScriptのキーと値のペア式になっているのだ。でも、これこそが問題の原因。なぜなら、Rubyでのキーと値のペア式はJavaScriptのそれとは違うからだ。皆さんもご存知かと思うが、下記のようなシンタックスRubyのやり方。

{"id"=>841425155, "email"=>"example@example.com", "created_at"=>"2018-12-30T23:07:17.457Z", "updated_at"=>"2018-12-30T23:07:17.457Z"}

そう、=>のような矢印でシンボルを表現するやり方だ。では、次にどうやったらRubyのわかる形に変更できるかについて見ていこう!!

うまくいく例

では、いったいどうやってRuby式のキーと値ペアにするのかというと、この関数を使うのだ。

JSON.parsse()

これは単純にJSONオブジェクトをRubyのわかるものに変えるだけなのだ。言うなら、to_json()関数の逆をやっているのだ。
よって、先ほど、puts response.bodyJavaScriptの形になっていたものはputs JSON.parse(response.body)と改めることで下記のようになる。

{"id"=>841425155, "email"=>"example@example.com", "created_at"=>"2018-12-30T23:07:17.457Z", "updated_at"=>"2018-12-30T23:07:17.457Z"}

つまり、うまくいくRspecのコードは下記のようになる。

    describe "POST create" do
        it "should create a user with valid params" do
            post :create, params: {user: {email: "example@example.com", password: "foobar", password_confirmation: "foobar"}}
            expect(response).to have_http_status(:created)
            expect(JSON.parse(response.body)['email']).to eq("example@example.com")
        end
    end


まとめ

どうだったでしょうか?
このように問題を発見することができれば、あとはしっかりとしたRubyの知識があればこのような問題は解決できる。なので、何かエラーに面したときはいろいろとプリントアウトしてみて、手掛かりを探しましょう!!
皆さんのテストコードがより素晴らしいものになることを祈ります。


ご精読ありがとうございました。では、また次回まで✌



Herokuのステージングブランチとは?(導入編)↓↓
programming-shop.hatenablog.com