check

【Rails】 RSpecの基本的な書き方

おつかれさまです

今日はRSpecの基本的な書き方をまとめておきます

RSpecとは

Railsで作成したモデルやコントローラーをrspecを書くことで自動テストしてくれます。このおかげで画面ぽちぽちしなくていいですね

https://github.com/rspec/rspec-rails

導入

まだ入れていない場合は、Gemfileに追加します。テスト用のデータを作るFactoryBotも一緒に入れておくと捗ります。

# Gemfile
group :development, :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
end
bundle install
bin/rails generate rspec:install

これで .rspecspec/spec_helper.rbspec/rails_helper.rb が作られます。テストファイルは spec/ 以下に ***_spec.rb という名前で置いていきます。

RSpecの実行

# 全ファイル実行
bundle exec rspec .

# ファイルを指定して実行
bundle exec rspec spec/models/***_spec.rb

覚えておくと便利なのがこのあたりです。

# 行番号を指定して、そのテストだけ実行する
bundle exec rspec spec/models/user_spec.rb:12

# 失敗したテストだけ再実行する
bundle exec rspec --only-failures

# ドキュメント形式で、何をテストしたか読める形で出力する
bundle exec rspec -f doc

行番号指定は、直したいテストだけを回せるので一番使います。--only-failures を使うには spec/spec_helper.rb に次の設定が必要です(コメントアウトされていることがあります)。

config.example_status_persistence_file_path = 'spec/examples.txt'

modelでテスト

RSpec.describe User, type: :model do
  describe 'テストの対象 メソッドとか' do
    context '~~の場合 activateメソッドとか' do
      let(:user) { create(:user) }

      # 事前処理したいことあればbefore
      before do
        #
      end

      it '~~になること 有効になる事とか' do
        user.activate
        expect(user.status).to eq 'active'
      end
    end
  end
end

出てきたキーワードはこういう意味です。

キーワード意味
describeテストの対象を書く
context特定の条件の内容
let変数的に使える。例だとspec/factoriesフォルダから作成されるuserが入る
it期待すること
eq期待値と実際値が等しいかどうか検証する

let と let! の違い

これは地味にハマるところです。

let(:user)  { create(:user) }  # 呼ばれたときに初めて実行される(遅延評価)
let!(:user) { create(:user) }  # 各テストの前に必ず実行される

letそのテストの中で参照されるまで実行されません。なので「一覧に1件表示されること」のようなテストで let を使うと、レコードが作られる前に一覧を取得してしまい、0件で落ちます。

テスト中で直接参照しないけれど、事前に存在していてほしいデータには let! を使います。

よく使うマッチャ

マッチャ用途
eq値が等しいexpect(1 + 1).to eq 2
include含んでいるexpect([1, 2]).to include 1
be_validバリデーションを通るexpect(user).to be_valid
be_present / be_blank中身があるかexpect(user.name).to be_present
change実行の前後で値が変わるexpect { user.save }.to change(User, :count).by(1)
raise_error例外が発生するexpect { foo }.to raise_error ArgumentError

上の4つは expect(値) の形、下の2つは expect { 処理 }ブロック形式です。ここを取り違えると動かないので注意してください。

Controllerでテスト

RSpec.describe UsersController, type: :controller do
  describe 'GET show' do
    context 'プロフィールが無効の場合' do
      let(:user) { create(:user, profile: 'inactive') }
      before { get :show, params: { id: user.id } }
      it 'プロフィールが表示されないこと' do
        expect(response).to have_http_status 200
        expect(response).to render_template :show
        expect(response.body.exclude?("#{user.name}のプロフィール")).to eq true
      end
    end
  end
end

elasticsearchが絡むテストは、メタデータを付けて分けておくと扱いやすいです。

describe 'GET search', elasticsearch: true do
  # ...
end

いまから書くなら request spec

ひとつ補足しておくと、コントローラのテストは今は request spec で書くのが主流です。rspec-rails的にも controller spec より request spec が推奨されています。

違いは、controller specがコントローラだけを切り出して呼ぶのに対し、request specはルーティングからミドルウェアまで通した実際のリクエストを投げる点です。本番に近い形で検証できます。

RSpec.describe 'Users', type: :request do
  describe 'GET /users/:id' do
    let(:user) { create(:user) }

    it '200が返り、名前が表示されること' do
      get user_path(user)

      expect(response).to have_http_status :ok
      expect(response.body).to include user.name
    end
  end
end

get :show, params: { id: user.id } のようにアクション名で呼ぶのではなく、get user_path(user)実際のURLで叩くのがポイントです。

既存のcontroller specが動いているならそのままで問題ありませんが、新しく書くならrequest specを選んでおくのが無難です。

ps: 共通処理で使えるinclude_contextらへんもまとめたいところ

→ まとめました: 【Rails】RSpecで忘れがちsubject, shared_examples, include_context

1件のコメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です