Zenn
📑

RSpecテスト②コード解説(FactoryBot,Faker,let,Capybara)

2025/03/05に公開

前回
https://zenn.dev/eliri/articles/dcbea711fda0c6
上記は、「RSpecテストとは何か・テストの種類・導入方法」をまとめただけの状態で
今回はコードの中身を解説していきます。

RSpecの構成・コード解説

2024年12月04日Qiitaでまとめたこと

https://qiita.com/___Eliri/items/e5f81afe21b490c3cdb8

RSpecテストとは

Railsでよくつかわれるテストツール。

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

「人間が読んでも分かりやすいコードでテストを書くこと」を目的としている。
Gemをインストールして、簡単にテストできる。

describe

これから○○のテストを始めるよと、テストのタイトル的な感じ。

describe 'ログインのテスト' do
end
describe '新規登録のテスト' do
end

describe '商品管理機能のテスト' do
end

context

describeをパターン分けをして、状況ごとにテストの流れを整理する。
ログインのテストなら、成功・失敗などパターンが発生するので、下記のように分岐させる。

describe 'ログインのテスト' do
    context 'ログイン成功した場合のテスト' do
    end
    context 'ログイン失敗した場合のテスト' do
    end
end

describe '商品管理機能のテスト' do
    context '商品が正しく登録される場合のテスト' do
    end
    context '商品登録が失敗する場合のテスト' do
    end
end

it

パターン分けした後に、どのような状態になってることを確認したいのか、具体的に。

describe 'ログインのテスト' do
    context 'ログイン成功した場合のテスト' do
        it 'ログイン後のリダイレクト先が正しいか' do
        end
    end
    context 'ログイン失敗した場合のテスト' do
        it 'ログイン失敗後のリダイレクト先が正しいか' do
        end
    end
end

describe '商品管理機能のテスト' do
    context '商品が正しく登録される場合のテスト' do
        it '保存された商品の情報が正しいか' do
        end
    end
    context '商品登録が失敗する場合のテスト' do
        it 'エラーメッセージが正しいか' do
        end
    end
end

expect( ).to と eq

expect( ).toは、( )内を期待値とする。
eqは、その期待とeqの後に続く値が同じかを確認。

expect( ).toエクスペクテーション
eqマッチャーと呼ぶ。

下記は、「ログイン後、example.com/users/1にリダイレクトする」ようチェックしてる

current_path
現在のページのパスを取得する。

.to eq
その後の値と等しいか確認。この場合「current_path」と等しいか。

'/users/' + user.id.to_s
ユーザIDをto_sで文字列にし、それをusersと結合する。
例えば、user.id が 1 の場合、「/users/1」とする。

describe 'ログインのテスト' do
    context 'ログイン成功した場合のテスト' do
        it 'ログイン後のリダイレクト先が正しいか' do
            expect(current_path).to eq '/users/' + user.id.to_s
        end
    end
end

describe '商品管理機能のテスト' do
  context '商品が正しく登録される場合のテスト' do
    it '保存された商品の情報が正しいか' do
      product = Product.create(name: 'サンプル商品', price: 1000)

      # 商品の名前と価格が正しいかを確認
      expect(product.name).to eq('サンプル商品')
      expect(product.price).to eq(1000)
    end
  end

  context '商品登録が失敗する場合のテスト' do
    it 'エラーメッセージが正しいか' do
      # 商品名が空の場合
      product = Product.create(name: '', price: 1000)

      # 商品名が空だとエラーメッセージが表示されることを確認
      expect(product.errors[:name]).to eq(["can't be blank"])
    end
  end
end

他にもよく使われるマッチャー:

  • be:
    オブジェクトが同一であるかを確認(例: expect(obj1).to be obj2)。
  • include:
    配列やハッシュに特定の要素が含まれているかを確認(例: expect(array).to include value)。
  • match:
    正規表現に一致するかを確認(例: expect(string).to match /pattern/)。

before

「フック」と呼ぶ。
個々のテストを実行する前に、まとめて共通する部分の前処理をする。
例えば、以下のようなログインのテストの場合、ログインするためのメールアドレスや名前が必要になる。
beforeを使うと、テストの度毎回書く必要がなく、まとめて書くことができる。

  • fill_in 'user[name]', with: user.name

    • 「名前」入力欄に、user.name(テスト用のユーザーの名前)を入力させる。
  • fill_in 'user[password]', with: user.password

    • 「パスワード」入力欄に、user.password(テスト用のユーザーのパスワード)を入力させる。
  • click_button 'Log in'

    • 「ログイン」ボタンをクリックさせる。

beforeを使わないと…

# beforeを使わなかった場合
describe 'ログインのテスト' do
  context 'ログイン成功した場合のテスト' do
    it 'ログイン後のリダイレクト先が正しいか' do
      # ログイン処理を毎回書く必要がある
      fill_in 'user[name]', with: user.name
      fill_in 'user[password]', with: user.password
      click_button 'Log in'
      
      expect(current_path).to eq '/users/' + user.id.to_s
    end

    it 'ログイン後にユーザー名が表示されるか' do
      # ここでも同じ処理を繰り返し書かないといけない
      fill_in 'user[name]', with: user.name
      fill_in 'user[password]', with: user.password
      click_button 'Log in'

      expect(page).to have_content user.name
    end
  end
end

beforeを使うと…

# beforeを使った場合
describe 'ログインのテスト' do
  context 'ログイン成功した場合のテスト' do
    # ここにまとめて書ける!コードが簡潔!
    before do
      fill_in 'user[name]', with: user.name
      fill_in 'user[password]', with: user.password
      click_button 'Log in'
    end

    it 'ログイン後のリダイレクト先が正しいか' do
      expect(current_path).to eq '/users/' + user.id.to_s
    end

    it 'ログイン後にユーザー名が表示されるか' do
      expect(page).to have_content user.name
    end
  end
end
describe 'テストのタイトル(これから○○のテストを始める)' do
  context 'describeを状況ごとにパターン分け(ログインなら成功・失敗など)' do
    it 'パターン分け後に、どのような状態になってることを確認したいのか' do
      # ○○が★★になってるかを確認
      expect(○○).to eq('★★')
      # expect(実際の値).to eq('期待値') = 実際の値が期待値になってるか確認
      # expect( ).to を エクスペクテーション、
      # eq を マッチャーと呼ぶ。
    end
  end
end

FactoryBot

FactoryBotは、「テストデータを簡単に作成するためのライブラリ」。
https://github.com/thoughtbot/factory_bot_rails
テストデータ作成をサポートするためのGem。 Gemfileに以下を記載する。

group :test do
  gem "factory_bot_rails"
end

FactoryBotを使用するには、「spec/factories/モデル名.rb」のファイルを作成する。

通常、データベースを使ったテストを行う場合、モデルのインスタンスを作成する必要があるが、FactoryBotを使うと、繰り返し同じようなデータを簡単に生成できる。

FactoryBot.define do
    factory :task do
        title { Faker::Lorem.characters(number:10) }
        keyword1 { Faker::Lorem.characters(number:10) }
        keyword2 { Faker::Lorem.characters(number:10) }
        keyword3 { Faker::Lorem.characters(number:10) }
    end
end

Faker

ランダムなテストデータ(名前、住所、メールアドレス、文章など)を生成するためのRubyのライブラリ。テストとかシードデータ(db/seeds.rb)を作るときに便利。

https://github.com/faker-ruby/faker

テスト用のダミーデータを作成するためのGem。 Gemfileに以下を書くことで使用可能。

group :test do
  gem 'faker'
end
FactoryBot.define do
    factory :task do
        title { Faker::Lorem.characters(number:10) }
        keyword1 { Faker::Lorem.characters(number:10) }
        keyword2 { Faker::Lorem.characters(number:10) }
        keyword3 { Faker::Lorem.characters(number:10) }
    end
end

Lorem

lorem ipsumの略。(ダミーテキスト(仮の文章)の英語表現)
ランダムな文章や単語を生成するための Faker のモジュール。
Loremの後にダミーテキストの種類を書く。

Fakerは、ランダムな文字列だけでなく、下記のように名前やメアドのダミーデータも作成できる。

Faker::Name.name # => "John Doe"
Faker::Internet.email # => "mike.ross@example.com"

モデルファイルのテスト

  • バリデーションやメソッドの動作をチェック
  • specフォルダ内に「modelフォルダ」を作成する。
  • モデル名+_spec.rb」というファイルを作成する。
  • ファイルの先頭に下記を記入。
    設定などを行うファイル(spec/rails_helper.rb)を読み込んでいる。
require 'rails_helper'

コントローラファイルのテスト

  • リクエストを送ったときのレスポンスをチェックする
  • specフォルダ内に「systemフォルダ」を作成する。
  • モデルファイルのテストと同様に「モデル名+_spec.rb」というファイルを作成する。

let

let(遅延評価)

  • let(:user) は it の中で初めて使われたときに create(:user) が実行される。
    • プログラムが「 it 'ログイン成功後にユーザーページに遷移する' do」のコードを実行された時に初めてletが実行されてるということ
  • fill_in 'Email', with: user.email で user を使った時点で user が作成される。
  • テストを実行するまでデータが作られないので、不要なデータを減らせる!

describe 'ログインのテスト' do
  let(:user) { create(:user, email: 'test@example.com', password: 'password') }

  it 'ログイン成功後にユーザーページに遷移する' do
    visit login_path
    fill_in 'Email', with: user.email
    fill_in 'Password', with: 'password'
    click_button 'ログイン'

    expect(current_path).to eq(user_path(user)) # ユーザーページに遷移することを確認
  end
end

let!(事前評価)

  • let!(:user) は it が実行される前に create(:user) が実行される。
  • user を it の中で使う前にデータが作られているので、最初からDBに user が存在する状態。
  • it 内で user を使わなくても、データが作成されるので無駄が増える可能性がある。
describe 'ログインのテスト' do
  let!(:user) { create(:user, email: 'test@example.com', password: 'password') }

  it 'ログイン成功後にユーザーページに遷移する' do
    visit login_path
    fill_in 'Email', with: user.email
    fill_in 'Password', with: 'password'
    click_button 'ログイン'

    expect(current_path).to eq(user_path(user)) # ユーザーページに遷移することを確認
  end
end

letとbefore

bforeと似ているが、beforeはitはブロックの実行時に都度実行されるので、it内で使用してない変数でも実行されてしまう。⇒不要なデータが毎回作成されるので、テストが遅くなる。

let!とFactoryBot

spec/rails_helper.rbに、下記を追加すればFactoryBotを省略することができる

RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end
# ↓これを
let!(:book) { FactoryBot.create(:book,title:'hoge',body:'body') }
# ↓このように省略できる
let!(:book) { create(:book,title:'hoge',body:'body') }

Capybara

カピバラ…?
結合テスト(E2Eテスト)を自動化するためのツール。
人間が手動で操作するのと同じ流れをテストできる!

例えば、この流れでテストしたい

  1. ログインページにアクセス
  2. メールアドレスとパスワードを入力
  3. ログインボタンをクリック
  4. ユーザーのマイページに遷移するか確認

📌 Capybaraのコード例(ログインテスト)

require 'rails_helper'

RSpec.describe 'ログイン機能', type: :system do
  let!(:user) { create(:user, email: 'test@example.com', password: 'password') }

  it '正しくログインできる' do
    visit login_path                  # 1. ログインページにアクセス
    fill_in 'Email', with: user.email # 2. メールアドレスを入力
    fill_in 'Password', with: 'password' # 3. パスワードを入力
    click_button 'ログイン'           # 4. ログインボタンを押す

    expect(current_path).to eq(user_path(user)) # 5. ユーザーページに遷移しているか確認
  end
end

E2E(End-to-End)テストとは?

アプリケーション全体の動作をユーザー目線でテストする方法。
ログイン、データ入力、ボタンのクリック、画面遷移など、実際のユーザーが操作する流れをシミュレートしてテストする。

他のテストの違い

テストの種類 何をテストする? ツール
ユニットテスト 個々のメソッドやクラスの動作をテスト RSpec(type: :model
結合テスト 複数の機能を組み合わせた処理をテスト(例: ユーザー登録 → ログイン) RSpec(type: :request
E2Eテスト ユーザー目線でアプリ全体の流れをテスト(ボタンクリック、遷移、表示など) Capybara(type: :system

Capybaraの実行環境(ドライバ)

Capybaraが動作するには、ドライバ(テストを動かす環境)が必要になる。

ドライバ名 特徴
Rack::Test Rails標準のテスト機能で、高速に動作。JavaScriptは動かせない。
Selenium 実際のブラウザ(Chromeなど)を起動して動作確認できる。JavaScriptも対応。ただし動作が遅い。

以下はRack::Test のやり方。

まずspec/spec_helper.rbに設定をする。

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :rack_test
  end
end

rails newしたデフォルトのGemfileは、元々下記状態になっている。
これでテストすると、Seleniumを使用してテストが行われる。

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 3.26'
  gem 'selenium-webdriver', '>= 4.0.0.rc1'
  # Easy installation and use of web drivers to run system tests with browsers
  gem 'webdrivers'
end

RSpecでよく使われるマッチャー名・メソッド

マッチャー名 / メソッド 機能(使い方)
be_valid モデルが有効であるか(expect(model).to be_valid)。
be_invalid モデルが無効であるか(expect(model).to be_invalid)。
include 配列や文字列に指定の値が含まれているか。
find ページ内の要素を検索する(find('.classname')など)。
click 指定の要素をクリックする。
have_content ページに特定の文字列が存在するか。
have_link 指定の値のリンクが存在するか(expect(page).to have_link('リンク名', href: path))。
eq 期待値と値を比較して一致するか(expect(x).to eq(y))。
have_selector HTMLタグやCSSに指定の文字列が存在するか(expect(page).to have_selector('h1', text: 'タイトル'))。
have_field 指定の入力フォームが存在するか(expect(page).to have_field('email'))。
find_all ページ上の指定のHTMLタグを全て取得する(find_all('div'))。
match 正規表現を使って期待値と一致するか確認する(expect(text).to match(/正規表現/))。
have_button ページ上に指定のボタンが存在するか(expect(page).to have_button('送信'))。
click_button 指定のボタンをクリックする(click_button '送信')。
have_current_path 現在のパスが期待通りか(expect(current_path).to eq('/home'))。
change ある動作で値が変化するか(expect { action }.to change { Model.count }.by(1))。
redirect_to リダイレクト先のURLを確認(expect(response).to redirect_to(path))。
have_http_status HTTPステータスコードを確認(expect(response).to have_http_status(200))。
render_template 特定のビューが描画されることを確認(expect(response).to render_template(:index))。
not_to be_valid モデルが無効であることを確認(expect(model).not_to be_valid)。
before テスト実行前に準備処理を実行する。
let 遅延評価される変数を定義する(it 内で呼ばれるまで実行されない)。
let! 事前評価される変数を定義する(it 実行前に評価される)。
create FactoryBot でデータを作成する(DB に保存)。
build FactoryBot でデータを作成するが、DB には保存しない。
create_list FactoryBot で複数のデータを作成する(create_list(:model, 数))。
attributes_for FactoryBot でハッシュ形式のデータを生成する。

参考文献

https://zenn.dev/merume/articles/f851abdf73bf8a

Discussion

ログインするとコメントできます