ryota21silvaの技術ブログ

Funna(ふんな)の技術ブログ

これまで学んだ技術の備忘録。未来の自分が救われることを信じて

【Rails/Rspec】リクエストスペックでAPIをテストする際に、よくやること

リクエストスペックの特徴

  • HTTP 動詞に対応するメソッド(get 、post 、delete 、 patch)を使う。
  • capybaraは使わない。
  • HTTPのレスポンスのステータスコードと実際のデータを含んだレスポンスボディを返す
200: OK - リクエストは成功し、レスポンスとともに要求に応じた情報が返される。
401: Unauthorized - 認証失敗。認証が必要である。
403: Forbidden - 禁止されている。リソースにアクセスすることを拒否された。
404: Not Found - 未検出。リソースが見つからなかった。

リクエストスペックでよく使うヤツ

  • JSON.parse

    与えられた JSON 形式の文字列を Ruby オブジェクトに変換して返す。
    JSON形式の文字列からRubyのHashへ変換することで、WebAPIで取得したJSONデータをRubyで使える様にする

  • リクエストヘッダーをセットしておく。

    →ヘッダ情報がないとテストが通らないことがある。

get api_v1_articles_path, headers: {
      'CONTENT_TYPE': 'application/json',
      'ACCEPT': 'application/json'
    }
(spec/requests/api/v1/articles_spec.rb)
require 'rails_helper'

RSpec.describe "Api::V1::Articles", type: :request do
  describe "GET /api/v1/articles" do
    it "掲示板一覧を返す" do
      create_list(:article, 10, user: user)
      get api_v1_articles_path, headers: {
          'CONTENT_TYPE': 'application/json',
          'ACCEPT': 'application/json'
        }
      // JSON.parse(body)でもいけたけど、response.bodyにしておく
      json = JSON.parse(response.body)
      expect(response).to have_http_status(200)
      expect(json["data"].count).to eq(10)
    end

  end
end
  • letを使って、headers情報を格納しておく。

let(:headers) do
  { 'Content-Type' => 'application/json',
    'Accept' => 'application/json'
  }
end
  • letで数字を変数(numとか)に入れておくのもアリ

RSpec.describe 'Api::V1::Articles', type: :request do
  describe 'GET /articles' do
    let(:article_num) { 10 }

    before do
      user = create(:user)
      create_list(:article, article_num, user: user)
    end

    it 'returns fincle articles in json format' do
      get api_v1_articles_path, headers: { CONTENT_TYPE: 'application/json', ACCEPT: 'application/json', }

      expect(JSON.parse(body)['data'].count).to eq(article_num)
      expect(response).to be_successful
      expect(response).to have_http_status(:ok)
    end
  end
end

上記テストの、JSON.parse(response.body)["data"]の結果

JSON.parse(response.body)["data"]
=> [{"id"=>"1", "type"=>"article", "attributes"=>{"title"=>"title-1", "contents"=>"contents-1", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"1", "type"=>"user"}}}},
 {"id"=>"2", "type"=>"article", "attributes"=>{"title"=>"title-2", "contents"=>"contents-2", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"2", "type"=>"user"}}}},
 {"id"=>"3", "type"=>"article", "attributes"=>{"title"=>"title-3", "contents"=>"contents-3", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"3", "type"=>"user"}}}},
 {"id"=>"4", "type"=>"article", "attributes"=>{"title"=>"title-4", "contents"=>"contents-4", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"4", "type"=>"user"}}}},
 {"id"=>"5", "type"=>"article", "attributes"=>{"title"=>"title-5", "contents"=>"contents-5", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"5", "type"=>"user"}}}},
 {"id"=>"6", "type"=>"article", "attributes"=>{"title"=>"title-6", "contents"=>"contents-6", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"6", "type"=>"user"}}}},
 {"id"=>"7", "type"=>"article", "attributes"=>{"title"=>"title-7", "contents"=>"contents-7", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"7", "type"=>"user"}}}},
 {"id"=>"8", "type"=>"article", "attributes"=>{"title"=>"title-8", "contents"=>"contents-8", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"8", "type"=>"user"}}}},
 {"id"=>"9", "type"=>"article", "attributes"=>{"title"=>"title-9", "contents"=>"contents-9", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"9", "type"=>"user"}}}},
 {"id"=>"10", "type"=>"article", "attributes"=>{"title"=>"title-10", "contents"=>"contents-10", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"10", "type"=>"user"}}}}]
  • 記事詳細のレスポンスにJSON.parse

[7]  JSON.parse(body)
=> {"data"=>{"id"=>"1", "type"=>"article", "attributes"=>{"title"=>"title-11", "contents"=>"contents-11", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"2", "type"=>"user"}}}}}

[8]  JSON.parse(body)["data"]
=> {"id"=>"1", "type"=>"article", "attributes"=>{"title"=>"title-11", "contents"=>"contents-11", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"2", "type"=>"user"}}}}

[9]  JSON.parse(body)["data"]["id"]
=> "1"
[10]  JSON.parse(body)["data"]["attributes"]
=> {"title"=>"title-11", "contents"=>"contents-11", "status"=>"draft"}

[11]  JSON.parse(body)["data"]["attributes"]["title"]
=> "title-11"

レスポンスボディ?の数
[12] JSON.parse(body).count
=> 1

relationship
[14] JSON.parse(body)["data"]["relationships"]
=> {"user"=>{"data"=>{"id"=>"2", "type"=>"user"}}}

ステータスコード
[3]  response.status
=> 200

attributes_for

POSTメソッドで、 - ファクトリからテスト用の属性値をハッシュとして作成する。複数のパラメータを送信する場合に、個々にパラメータ名を付けるのではなく、キーと値を組み合わせたハッシュを作成してくれる。 - buildならモデルオブジェクトを作成する。

https://qiita.com/_kotaro/items/adbc355bfb550b8b2150

FactoryBotのデータ

(spec/factories/users.rb)
FactoryBot.define do
  factory :user do
    sequence(:name) { |n| "user-#{n}" }
    sequence(:email) { |n| "user-#{n}@example.com" }
    password { 'password' }
  end
end
  • attributes_for
let!(:user_attributes) { attributes_for(:user) }

=> {:name=>"user-1", :email=>"user-1@example.com", :password=>"password"}
  • build
let!(:user_attributes) { build(:user) }

=> #<User:0x00007fcf5a68cff0 id: nil, name: "user-1", email: "user-1@example.com", crypted_password: nil, salt: nil, created_at: nil, updated_at: nil>
  • create
let!(:user_attributes) { create(:user) }

=> #<User:0x00007fb222ebcf18
 id: 1,
 name: "user-1",
 email: "user-1@example.com",
 crypted_password: "$2a$04$rslb/9FwCJVJFYmR.LeLR.8JjYOt2kEYCh8d2F5E1JJGRJ1lZ/BiS",
 salt: "hoCH88wcyx3PFVqxduem",
 created_at: Sun, 26 Jul 2020 02:15:36 UTC +00:00,
 updated_at: Sun, 26 Jul 2020 02:15:36 UTC +00:00>

to_json

RubyのHash形式のデータを、JSON形式の文字列に変換してくれる。

[1]  user_attributes
=> {:name=>"user-1", :email=>"user-1@example.com", :password=>"password"}
[2]  user_attributes.to_json
=> "{\"name\":\"user-1\",\"email\":\"user-1@example.com\",\"password\":\"password\"}"