【Rails】RSpecを使った自動テスト《システムスペック編》

はじめに

Railsでアプリケーションを開発する際、品質を担保するためにテストは欠かせません。その中でも、RSpecは多くの開発者に愛用されているテストフレームワークです。

今回は、RSpecを使ったRailsアプリケーションのテスト方法について、実践的な視点から解説していきます。

システムスペック

Capybara

RSpecのシステムスペックでは、Capybaraを使用してブラウザベースのエンドツーエンド(E2E)テストを実行します。Capybaraは、ウェブアプリケーションのユーザーインターフェースをテストするためのツールで、実際のユーザーが行う操作をシミュレートします。

まず、rails_helper.rbにCapybaraの設定を追加します。

spec/rails_helper.rb

require 'capybara/rspec'

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

  config.before(:each, type: :system, js: true) do
    driven_by(:selenium_chrome_headless)
  end
end

ここでは、ユーザーがサインアップ、ログイン、ログアウトする一連の操作をテストするシステムスペックを示します。

まず、簡単なユーザーコントローラーと対応するビューを用意します。

app/controllers/users_controller.rb

class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      session[:user_id] = @user.id
      redirect_to root_path, notice: 'User was successfully created.'
    else
      render :new
    end
  end

  def user_params
    params.require(:user).permit(:name, :email, :password, :password_confirmation)
  end
end

app/views/users/new.html.erb

<h1>Sign Up</h1>

<%= form_with(model: @user, local: true) do |form| %>
  <div>
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>
  <div>
    <%= form.label :email %>
    <%= form.text_field :email %>
  </div>
  <div>
    <%= form.label :password %>
    <%= form.password_field :password %>
  </div>
  <div>
    <%= form.label :password_confirmation %>
    <%= form.password_field :password_confirmation %>
  </div>
  <div>
    <%= form.submit "Sign Up" %>
  </div>
<% end %>

次に、RSpecとCapybaraを使用してこれらの操作をテストします。

spec/system/users_spec.rb

require 'rails_helper'

RSpec.describe 'User management', type: :system do
  describe 'sign up' do
    it 'allows a user to sign up' do
      visit new_user_path

      fill_in 'Name', with: 'Alice'
      fill_in 'Email', with: 'alice@example.com'
      fill_in 'Password', with: 'password'
      fill_in 'Password confirmation', with: 'password'
      click_button 'Sign Up'

      expect(page).to have_content('User was successfully created.')
      expect(page).to have_content('Welcome, Alice')
    end
  end

  describe 'log in and log out' do
    let!(:user) { User.create!(name: 'Alice', email: 'alice@example.com', password: 'password') }

    it 'allows a user to log in and log out' do
      visit login_path

      fill_in 'Email', with: 'alice@example.com'
      fill_in 'Password', with: 'password'
      click_button 'Log In'

      expect(page).to have_content('Welcome, Alice')

      click_link 'Log Out'

      expect(page).to have_content('Logged out successfully')
    end
  end
end
  1. ユーザーのサインアップ
    • visit new_user_pathでサインアップページにアクセスします。
    • フォームにユーザー情報を入力し、click_button 'Sign Up'でフォームを送信します。
    • サインアップが成功し、ユーザーが作成されたことを確認します。
  2. ユーザーのログインとログアウト
    • 事前にlet!(:user)でユーザーを作成しておきます。
    • visit login_pathでログインページにアクセスします。
    • フォームにログイン情報を入力し、click_button 'Log In'でフォームを送信します。
    • ログインが成功し、ユーザーがログイン状態であることを確認します。
    • click_link 'Log Out'でログアウトし、ログアウトが成功したことを確認します。

ユーザーインターフェースのテスト

RSpecのシステムスペックにおけるユーザーインターフェースのテストでは、主にCapybaraを使用してブラウザの振る舞いをシミュレートし、ユーザーが行う操作や期待される表示を確認します。

例として、以下のようなコントローラーとビューを考えます。

app/controllers/books_controller.rb

class BooksController < ApplicationController
  def index
    @books = Book.all
  end

  def show
    @book = Book.find(params[:id])
  end

  # 他のアクション(create, update, delete)も同様に実装する
end

app/views/books/index.html.erb

<h1>Books</h1>

<ul>
  <% @books.each do |book| %>
    <li><%= link_to book.title, book_path(book) %></li>
  <% end %>
</ul>

<%= link_to 'New Book', new_book_path %>

システムスペックでは、実際にブラウザをシミュレートして操作し、期待される振る舞いを確認します。

spec/system/books_spec.rb

require 'rails_helper'

RSpec.describe 'Books', type: :system do
  describe 'index page' do
    it 'displays a list of books' do
      books = create_list(:book, 3)  # 3つのダミーデータを作成する
      visit books_path

      books.each do |book|
        expect(page).to have_link(book.title, href: book_path(book))
      end
    end
  end

  describe 'show page' do
    it 'displays the book details' do
      book = create(:book, title: 'Sample Book', author: 'Sample Author')
      visit book_path(book)

      expect(page).to have_content('Sample Book')
      expect(page).to have_content('Sample Author')
    end
  end

  describe 'creating a new book' do
    it 'adds a new book to the list' do
      visit new_book_path

      fill_in 'Title', with: 'New Book'
      fill_in 'Author', with: 'New Author'
      click_button 'Create Book'

      expect(page).to have_content('New Book')
      expect(page).to have_content('New Author')
    end
  end

  describe 'editing a book' do
    it 'updates the book details' do
      book = create(:book, title: 'Old Title', author: 'Old Author')
      visit edit_book_path(book)

      fill_in 'Title', with: 'Updated Title'
      fill_in 'Author', with: 'Updated Author'
      click_button 'Update Book'

      expect(page).to have_content('Updated Title')
      expect(page).to have_content('Updated Author')
    end
  end

  describe 'deleting a book' do
    it 'removes the book from the list' do
      book = create(:book, title: 'Book to Delete')
      visit books_path

      within "#book_#{book.id}" do
        click_link 'Delete'
      end

      expect(page).not_to have_content('Book to Delete')
    end
  end
end
  1. インデックスページのテスト
    • visit books_pathで本の一覧ページにアクセスします。
    • 各本のタイトルがリンクとして表示されていることを確認します。
  2. 詳細ページのテスト
    • visit book_path(book)で本の詳細ページにアクセスします。
    • 本のタイトルと著者が表示されていることを確認します。
  3. 新規作成のテスト
    • visit new_book_pathで新規作成ページにアクセスします。
    • フォームにタイトルと著者を入力し、click_button 'Create Book'で本を作成します。
    • 作成した本のタイトルと著者が表示されていることを確認します。
  4. 編集のテスト
    • visit edit_book_path(book)で本の編集ページにアクセスします。
    • フォームに新しいタイトルと著者を入力し、click_button 'Update Book'で本を更新します。
    • 更新した本のタイトルと著者が表示されていることを確認します。
  5. 削除のテスト
    • visit books_pathで本の一覧ページにアクセスします。
    • 削除リンクをクリックして、本が一覧から削除されていることを確認します。

JavaScriptを含む動的な振る舞いのテスト

RSpecのシステムスペックでは、JavaScriptを含む動的な振る舞いをテストする場合、通常はブラウザを制御するためにCapybaraとSeleniumを組み合わせて使用します。

まず、CapybaraとSeleniumを使うための設定をrails_helper.rbに追加します。

spec/rails_helper.rb

require 'capybara/rspec'
require 'selenium-webdriver'

Capybara.register_driver :selenium_chrome_headless do |app|
  options = Selenium::WebDriver::Chrome::Options.new(args: %w[headless disable-gpu])
  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

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

capybara/rspecselenium-webdriverrequireし、Seleniumのドライバーを設定します。:selenium_chrome_headlessドライバーを登録し、テスト実行時にヘッドレスChromeを使用するように設定します。

以下は、JavaScriptを含む動的な振る舞いを持つフォームのテスト例です。

spec/system/books_spec.rb

require 'rails_helper'

RSpec.describe 'Books', type: :system, js: true do
  describe 'creating a new book asynchronously' do
    it 'adds a new book to the list without page reload' do
      visit new_book_path

      fill_in 'Title', with: 'New Book'
      fill_in 'Author', with: 'New Author'
      click_button 'Create Book'

      # フォームの送信後、非同期でリストに新しい本が追加されることを確認する
      within '#books-list' do
        expect(page).to have_content('New Book')
        expect(page).to have_content('New Author')
      end
    end
  end
end
  • RSpec.describeブロックでtype: :system,js: trueを指定し、システムスペックでJavaScriptを含む動的な振る舞いをテストします。
  • describeブロック内にテストケースを定義し、itブロックでテストの内容を記述します。
  • visitメソッドで対象のページにアクセスします。この例では、新規作成ページにアクセスしています。
  • fill_inメソッドでフォームの入力を行います。
  • click_buttonメソッドでフォームを送信します。
  • withinブロック内で、非同期で更新された部分のコンテンツが正しく表示されていることを確認します。

まとめ

RSpecを使いこなすことで、Railsアプリケーションの品質を大幅に向上させることができます。ただし、テストの書きすぎには注意が必要です。重要な機能や複雑なロジックに焦点を当て、バランスの取れたテスト戦略を立てることが大切です。

また、CIツールと組み合わせることで、継続的にテストを実行し、問題を早期に発見することができます。例えば、GitHubActionsを使えば、プッシュやプルリクエスト時に自動的にテストを実行できます。

Railsアプリケーション開発において、RSpecは強力な味方となります。ぜひ、日々の開発に取り入れて、より堅牢なアプリケーション作りを目指してください。

関連記事

【Rails】Paranoiaを使用した論理削除(ソフトデリート)
# はじめに Paranoiaは、Railsアプリケーションで論理削除(ソフトデリート)を実現するためのGemです。 論理削除は、データベースのレコードを物理的に削除するのではなく、削除フラグを設定することで「削除済み」とみなす方法です。こ [...]
2024年7月20日 21:33
【Rails】activerecord-multi-tenantを使用したマルチテナントアプリケーションの作成
# はじめに マルチテナントアプリケーションでは、複数の顧客(テナント)が同じアプリケーションを利用するため、データの分離が必要です。 activerecord-multi-tenantは、このようなマルチテナント環境をサポートするための便 [...]
2024年7月18日 16:50
【Rails】RubyとRailsにおけるattr_reader, attr_writer, attr_accessorの概念と使用方法
# はじめに RubyとRailsの開発において、`attr_reader`,`attr_writer`,`attr_accessor`は非常に便利なメソッドです。これらは、クラス内でインスタンス変数に対するゲッターおよびセッターメソッドを簡単に [...]
2024年7月17日 18:11
【Rails】RubyとRailsにおけるyieldの概念と使用方法
# はじめに RubyとRailsにおける`yield`は、メソッドやテンプレートの中で動的にコードブロックを実行する能力を提供し、これによってコードの再利用性と拡張性が大幅に向上します。本記事では、RubyとRailsにおける`yield`の概 [...]
2024年7月17日 13:15
【Rails】AASMを使用してオブジェクトの状態遷移を効率的に管理
# はじめに Railsアプリケーションにおいて、オブジェクトの状態管理は重要な課題の一つです。AASM (Acts As State Machine) gemは、複雑な状態遷移を効率的に管理します。本記事では、AASMの基本的な使い方を解説して [...]
2024年7月16日 18:00
【Rails】RSpec + Swagger + rswagでアプリケーションのAPIをテストおよびドキュメント化する方法
# はじめに Railsアプリケーションの開発において、APIのテストとドキュメント化は重要な要素です。 RSpecはテストフレームワークとして広く利用されており、SwaggerはAPIの設計とドキュメント化を支援します。これらを統合するr [...]
2024年7月16日 14:27
【Rails】mailcatcherを使用して開発環境でメール送信をテストする方法
# はじめに mailcatcherは、開発環境でのメール送信をキャプチャするためのツールです。ローカルで送信されたメールをブラウザ上で簡単に確認できるようにします。mailcatcherをRailsアプリケーションで使用する方法について説明しま [...]
2024年7月15日 16:37
【Rails】impressionistを使用してページビューやクリック数を追跡する方法
# はじめに impressionist Gemを使用してRailsアプリケーションでページビューやクリック数を追跡する方法について説明します。 # 実装方法 ## impressionist Gemのインストール まず、impre [...]
2024年7月15日 14:18