おつかれさまです
今日は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
これで .rspec と spec/spec_helper.rb、spec/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件のコメント