0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Rails] 自分流のrspec(API向け)テンプレート

Last updated at Posted at 2022-01-28

「APIのテスト書きたいけど、どんな感じでかけばいいのか忘れちゃった!」というときに、とりあえずこのテンプレをコピペしてからコーディングを始めることで超絶時短を狙う。

テンプレート

前提
FactoryBot利用し、Factoryのクラスメソッド実行時にFactory.を省略できるような設定をrails_helperでしている

spec/requests/【対象リソース名(スネークケース、複数形)】.rb
require 'rails_helper'

RSpec.describe "/<対象リソース名(スネークケース、複数形)>", type: :request do

  # xxx_xxxテーブルのレコード設定
  ## <レコードの説明>
  let!(:xxx_xxx1) { create(:<xxx_xxx>, :<trait>,...) }
  ## <レコードの説明>
  let!(:xxx_xxx2) { create(:<xxx_xxx>, :<trait>,...) }
  ...

  # yyy_yyyテーブルのレコード設定
  ## <レコードの説明>
  let!(:yyy_yyy1) { create(:<yyy_yyy>, :<trait>,...) }
  ## <レコードの説明>
  let!(:yyy_yyy2) { create(:<yyy_yyy>, :<trait>,...) }
  ...

  
  # APIリクエスト関連
  ## 認証トークン
  let!(:token) do
    <認証処理>
  end

  ## リクエストヘッダ(認証OK)
  let!(:valid_headers) { { 'Content-Type' => 'application/json', 'Authorization' => "Token #{token}" } }
  ## リクエストヘッダ(認証NG)
  let!(:valid_headers) { { 'Content-Type' => 'application/json', 'Authorization' => "Token #{token}a" } }

  ## ステータスコード
  let(:res_status) { response.status }
  ## レスポンスボディ(のJSON文字列をパースして、キーがシンボルであるハッシュとして受け取ったもの)
  let(:res_body) { JSON.parse(response.body, { symbolize_names: true }) }
  ## 認証エラー時のレスポンスボディ
  let!(:res_body_unauthorized) { { error: 'unauthorized' } }

    describe '<HTTPメソッド> /<アクション>' do
    context '正常系' do
      # リクエストパラメータ
      let!(:params) { { a: 1, b: 2,... }.to_json }
      # リクエストヘッダ
      let!(:headers) { valid_headers }


      # リクエスト(GET /index時)
      subject { get <リソース名(スネークケース,複数形)>_url, headers: headers }

      # リクエスト(GET /show時)
      # subject { get <リソース名(スネークケース,単数形)>_url(<表示対象のリソースのモデルインスタンス(ActiveRecord)>), params: params, headers: headers }

      # リクエスト(POST /create時)
      # subject { post <リソース名(スネークケース,複数形)>_url, params: params, headers: headers }

      # リクエスト(PUT /update時)
      # subject { put <リソース名(スネークケース,単数形)>_url(<更新対象のリソースのモデルインスタンス(ActiveRecord)>), params: params, headers: headers }

      # リクエスト(DELETE /delete時)
      # subject { delete <リソース名(スネークケース,単数形)>_url(<削除対象のリソースのモデルインスタンス(ActiveRecord)>), headers: headers }

      let(:expected_body) do
        {
          <期待するレスポンスボディの内容>
        }
      end

      it 'ステータスコード: <ステータスコード>, ボディ: <レスポンスボディの概要>, DB: <DBデータの変化概要>' do
        # リクエスト実行
        expect(subject).to change(<リソースのクラス名>, :size).by(<変化したレコード数>)
        # ステータスコードチェック
        expect(res_status).to eq(<ステータスコード>)
        # レスポンスボディチェック
        expect(res_body).to eq(expected_body)
      end
    end

    context '異常系: 認証エラー' do
      # リクエストパラメータ
      let!(:params) { { a: 1, b: 2,... }.to_json }
      # リクエストヘッダ
      let!(:headers) { invalid_headers }
      # リクエスト(GET /index時)
      subject { get <リソース名(スネークケース,複数形)>_url, headers: headers }

      it 'ステータスコード: <ステータスコード>, ボディ: <レスポンスボディの概要>, DB: レコードが作成されない' do
        # リクエスト実行
        expect(subject).to change(<リソースのクラス名>, :size).by(0)
        # ステータスコードチェック
        expect(res_status).to eq(401)
        # レスポンスボディチェック
        expect(res_body).to eq(res_body_unauthorized)
      end
    end

    context '異常系: リクエストパラメータ不備' do
    end
    ...

  end
end

補足

コードの構造にルールを設け、シンプルに保つことを心がける。

①階層構造(describe, context, it)

  1. **「チェック対象はdescribe, 条件はcontext, 期待結果はit」**と階層化する。

  2. DBのデータやリクエスト、レスポンスに関する定義など、準備はdescribe, contextで済ませておく

  3. itでは、リクエストを投げる → それによる結果をチェックするに注力する

②準備系の設定(let!, let, before)

後の階層で変数的に使いまわしたい設定は、let!での定義を基本とし、時間差が必要な場合のみletを使う
※letは、遅延評価をしっかり意識しないと期待しない挙動をしてしまい、泥沼にハマる可能性が高いため
※後から参照しなくてよい設定は、beforeで定義する慣習らしい。・・・が、コード書いてる内に後から「やっぱり必要でした!」というケースもあるし、let!でなくbeforeを使うメリットはさほどないと判断

③リクエストのスローとレスポンスのチェック(subject, response)

  1. subjectに、実際にリクエストを投げる処理を記述する
  2. subject内で記述する**APIのパスは、<bundle exec rails routeで表示されるパスの省略系>_url**でベタ書きせず書く
  3. 2.について、show, update, delete系のAPIは、その引数に操作対象リソースのモデルインスタンスを記述(xxx_url(<インスタンス>))してやるとidをベタ書きせずに書ける
  4. API実行結果であるレスポンスボディ、ステータスコードはresponseから参照する(response.status, response.body)
    subjectを使用してリクエストをスローすると、responseでその結果を受け取れる、というrspecの機能があるのでこれを利用
  5. レスポンスボディ(response.body)は、JSON文字列でありチェック時に扱いづらいため、キーはシンボルでハッシュ化する処理を定義しておく
  6. 期待するレスポンスボディの定義も、キーがシンボルなハッシュで記述して、3.と突き合わせる形でチェックする
  7. ステータスコードのチェックは、数値でベタ書きする(be_successとか書くと、200でも201でもチェック通過してしまい、曖昧性が排除できない)
  8. レスポンスのチェックは、「ステータスコード」「レスポンスボディ」「DBのレコード数変化」の3点とする。
    ※レコードの内容がなにかしら変化する系のアクション(create, update, delete)は、「DBの中身的にもホントに期待する結果になっているのか?」まで見たほうが良い気もするが、個数だけチェックしている記事が多いっぽいのでそうする(腑に落ちてはいない)。

メモ

ホントはheaderの定義もキーがシンボルのハッシュで書きたい。
しかし、なぜかContent-type:がうまくかけない。
なので、渋々キーが文字列のハッシュにした。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?