🦓

[Rails]rspecのセットアップから基本の書き方について

2023/06/06に公開

はじめに

rpecのセットアップ、モデルspecとシステムspecについてまとめました。

Rspecとは

RSpecは、テストを実行するためのテストフレームワークの一つです。
Railsアプリでは、RSpecを使ってモデル、ビュー、コントローラーなどのコンポーネントをテストすることが一般的です。

種類 用途
ユニットテスト 個々のコンポーネント(通常はモデル)をテスト
コントローラテスト コントローラーアクションの振る舞いをテスト
統合テスト アプリケーションの異なる部分が予想通りに連携して動作することをテスト
システムテスト ユーザーの視点からアプリケーションの機能をテスト
ビューテスト ビューのレンダリングをテスト
ヘルパーテスト ビューのヘルパーメソッドの振る舞いをテスト
メイラーテスト アプリケーションから送信されるメールの振る舞いをテスト
ジョブテスト バックグラウンドジョブ(例: Active Job)の正確性をテスト
キャッシュテスト キャッシュ戦略(例: フラグメントキャッシュ)が正常に機能することをテスト
ルーティングテスト アプリケーションのルーティングが正しく設定されていることをテスト

適切なテストの種類を選択し、アプリの要件と機能に合わせてテストを書きましょう。

流れ

  1. rspecに必要なgemをインストールする
  2. rspecをインストールする
  3. モデルspecを作成する
  4. テストを実行する
  5. システムspecを作成する
  6. 共通するコードをモジュール化する
  7. モデルspecとシステムspecの違い

gemをインストール

Gemfile
group :development, :test do
  gem 'byebug', platforms: %i[mri mingw x64_mingw]
  gem 'rspec-rails'
  gem 'factory_bot_rails'
end
bundle install

factory_bot_rails(以前はFactoryGirlとも呼ばれていました)は、Railsアプリのテストで使用されるGemで、モデルのテストデータを簡単に生成されます。

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

rspecをインストール

ターミナル
bundle exec rails generate rspec:install                                     
Running via Spring preloader in process 65071
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

三つのファイルを作成されましたね。

1. .rspec

テストディスクリプションを表示させる追記します。

.rspec
# spec/spec_helper.rbを読み込む記述
--require spec_helper
# テストディスクリプションを表示させる
--format documentation

2. spec/spec_helper.rb

基本的にいじらないです。

spec/spec_helper.rb
# railsを読み込まない
# rubyのクラスやオブジェクトなど
# rspecとruby用

3. spec/rails_helper.rb

FactoryBotのシンタックスをサポートさせる記述を追加します。

spec/rails_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'

require File.expand_path('../config/environment', __dir__)

# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!

# ここからrailsを読み込まれる

# user = FactoryBot.create(:user) 
# user = create(:user) # FactoryBotを略す
RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end

https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#configure-your-test-suite

モデルspecを作成する

名前の通り、モデルspecはモデルに対するテストのことです。
モデルはデータベーステーブルと連動し、データベース内のデータの操作やクエリを担当します。
モデルspecは、これらのモデルの振る舞いとデータ操作をテストするために使用されます。
Taskモデルに対してモデルスペックを作成します。

bundle exec rails generate rspec:model task                                   
Running via Spring preloader in process 65269
      create  spec/models/task_spec.rb
      invoke  factory_bot
      create  spec/factories/tasks.rb

初期のspec/models/task_spec.rb

spec/models/task_spec.rb
require 'rails_helper'

RSpec.describe Task, type: :model do
  # pending "add some examples to (or delete) #{__FILE__}"
end

初期のspec/factories/task.rb

spec/factories/task.rb
FactoryBot.define do
  factory :task do
    
  end
end

テストを実行する

# 全てのrspecを実行
bundle exec rspec
# 指定したrspecを実行
bundle exec rspec spec/models/task_spec.rb

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Task add some examples to (or delete) /Users/***/workspace/runteq/rspec_beginner/sample_app_for_rspec/spec/models/task_spec.rb
     # Not yet implemented
     # ./spec/models/task_spec.rb:4


Finished in 0.00251 seconds (files took 1.16 seconds to load)
1 example, 0 failures, 1 pending

ここまでひと通り実行できましたらセットアップが完了です。
ここからテストコードを書いていきます。

spec/factories/task.rbにテストデータを作成する

spec/factories/task.rb
FactoryBot.define do
  factory :task do
    sequence(:title, "title_1")
		# 順序でユニークな値を生成する
		sequence(:title) { |n| "title_#{n}" }
    content { 'Content' }
    status { 'todo' }
    deadline { 1.week.from_now }
    association :user
  end
end

# factoryの名前をモデル名とは別にしたい場合は、以下のようにクラスを指定する
FactoryBot.define do
  factory :admin, class: "Task"

https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#defining-factories
https://devhints.io/factory_bot
https://osamudaira.com/525/

spec/models/task_spec.rbにテストコードを作成する

spec/models/task_spec.rb
RSpec.describe Task, type: :model do
  describe 'validation' do
    it 'is valid with all attributes' do
    	# NG 一つのテスト失敗したら次の実行が中断される
	task = Task.new(title: 'New Task', content: 'New Task Content', status: 0)
	# 1行に1つずつテストコードを書く
	expect(Task.title).to eq 'title_1'
	expect(Task.content).to eq 'Content'
	# enum status: { todo: 0, doing: 1, done: 2 }
	expect(Task.status).to eq 0 
	# buildを使う
	task = build(:task)
	expect(task).to be_valid
   end
  end
end

createbuildの違い

build:データベースに保存されない、rails consoleidnilで分かる。
create:データベースに保存される、rails consoleidがあることで分かる。

マッチャ

# aはbと等しい値に期待する
expect(a).to eq(b)

マッチャ一覧

https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/

ここまではモデルspecでした。
続いてシステムspecについてまとめます。

capybaraをインストール

Gemfile
gem 'capybara'
bundle install

Capybaraは、Railsの統合テストやシステムテストなどのコンテキストで使用されます。
Capybaraは、テストケースをシミュレートし、クリック、フォームの入力、ボタンのクリックなど、ユーザーがウェブアプリケーション上で行う操作をシミュレートできます。複数のブラウザドライバーをサポートしており、異なるブラウザでテストを実行できます。

https://github.com/teamcapybara/capybara

システムspecファイルを作成する

rails generate rspec:system task

Running via Spring preloader in process 98275
      create  spec/system/tasks_spec.rb

spec/system/tasks_spec.rb

ユーザーをログインしてからタスクを作成までの動きをテストします。

spec/system/tasks_spec.rb
require 'rails_helper'

RSpec.describe 'Tasks', type: :system do
 describe 'logged in' do
  before { login_as(user) }

  it 'allows the user to create a new task' do
    visit new_task_path
    fill_in 'Title', with: 'Test task'
    fill_in 'Content', with: 'Test content'
    fill_in 'Deadline', with: DateTime.new(2023, 6, 1, 10, 30)
    select 'todo', from: 'Status'
    click_button 'Create Task'

    expect(page).to have_content('Task was successfully created.')
    expect(page).to have_content('Test task')
    expect(page).to have_content('Test content')
    expect(page).to have_content 'Deadline: 2023/6/1 10:30'
  end
 end
end

create_listを使って複数のインスタンスを作成する

crete_listはFactoryBotで用意されているメソッドです。
作成されたインスタンスは配列になります。

spec/system/tasks_spec.rb
RSpec.describe 'Tasks', type: :system do
 describe 'logged in' do
  before { login_as(user) }
     context 'tasks page' do
        it 'show all tasks' do
          task_list = create_list(:task, 3)
          visit tasks_path
          expect(page).to have_content task_list[0].title
          expect(page).to have_content task_list[1].title
          expect(page).to have_content task_list[2].title
          expect(current_path).to eq tasks_path
        end
      end
 end
end

コンソールでFactoryBotを使用したいときは、require 'factory_bot_rails'を入力しなければ使うことはできません。

require 'factory_bot_rails'
include FactoryBot::Syntax::Methods

特定のテストだけを走らせたい時に

spec_helper.rbに記述を追加します。

spec/spec_helper.rb
RSpec.configure do |config|
...
  # This allows you to limit a spec run to individual examples or groups
  # you care about by tagging them with `:focus` metadata. When nothing
  # is tagged with `:focus`, all examples get run. RSpec also provides
  # aliases for `it`, `describe`, and `context` that include `:focus`
  # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
  config.filter_run_when_matching :focus
=begin
  # Allows RSpec to persist some state between runs in order to support
...
  Kernel.srand config.seed
=end
end

実行したいテストにfocus: trueを追加します。

RSpec.describe Task do
  it '...' do
    # ...
  end

  it '...', focus: true do
    # ...
  end
end

共通するテストーコードをモジュール化にしてspec/supportに入れる

複数のテストに共通するloginメソッドをmoduleとしてまとめていきます。

spec/rails_helper.rbの設定を変える

spec/rails_helper.rb
# supportファイルを読み込む
# 25行目にあるコードがコメントアウトされています。コメントアウトを解除します
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

# 読み込むmoduleを追加する
RSpec.configure do |config|
  config.include LoginMacros
end

login_support.rbを作成する

spec/support/login_macros.rb
module LoginMacros
  def login_as(user)
    visit root_path
    click_link 'Login'
    fill_in 'Email', with: user.email
    fill_in 'Password', with: 'password'
    click_button 'Login'
  end
end

テストコードにlogin_asメソッドを読み込む

RSpec.describe 'Users', type: :system do
...
  describe 'logged_in' do
    before { login_as(user) }
...   

モデルspecとシステムspecの違い

モデルspecは、モデルのバリデーション、関連付け、メソッド、スコープなどの動作をテストします。一方、システムspecは、ユーザーがアプリケーションを使用する際のテストするために使用されます。例えば、システムspecでは、ユーザーが特定のアクションを実行した場合の画面遷移、データベースの変更、フラッシュメッセージの表示などをテストします。
モデルspecはモデルの単一のコンポーネントをテストし、システムspecはエンドツーエンドのユーザーシナリオをテストします。

終わりに

rspecのインストールからセットアップまでまとめてみました。
次回では例を見ながら復習していきたいと思います。

https://zenn.dev/igaiga/books/rails-practice-note/viewer/rails_rspec_workshop

Discussion