Railsのテストの書き方

前提知識

Railsのテストは、Ruby標準のテスト機能であるMinitestをWeb向けに拡張したもので、両方のリファレンスを活用します。

  • Railsテスティングガイド
    • Railsのテスト方法に関する網羅的なドキュメント
  • Minitest::Assersions
    • Ruby標準のアサーション機能であるMinitestのリファレンス。
    • 実行結果の妥当性をチェックする処理をアサーションと言う
    • RSpecはRuby標準・Rails標準とは別物であり、組み合わせて使うことはない
  • Rails標準のテスト準備
    • 多くのケースでデータベースのセットアップは必要
    • データ作成に手間をかけることは非推奨と思われる

事前セットアップ

setupメソッドで事前処理、teardownメソッドで事後処理を書けます。 Webアプリケーションでは、状態に応じて挙動を変えるケースが多々あります。以下のようにclassを階層化することで、複数の状態をセットアップできます。

class SomeTest < ActiveSupport::TestCase
  class ValidCase < self
    setup do
      # セットアップA
    end
    
    test "OK with valid settings" do
    end
  end
  
  class InvalidCase < self
    setup do
      # セットアップB
    end
    
    test "NG with invalid settings" do
    end
  end
end

この例で、ValidCase内に書いたtestブロックは、セットアップAの状態でスタートし、InvalidCase内に書いたtestブロックは、セットアップBの状態でスタートします。

なお、テストケースでDBに加えられた変更はケース終了時にROLLBACKが走るため、手動でteardownで消す必要はありません。2番目に書いたテストケースが1番目の処理結果に基づくような書き方をするのも間違っています。

インテグレーション・テストの書き方

RailsはWeb特有のインテグレーション・テストの書き方をサポートしています。Webリクエストの中には複数の処理が含まれることが多く、一連のフロー中にエラーがないことを確認します。比較的少ないテストケースでカバレッジが上がりやすく、テスト効率は優れています。

そのためには、ActionDispatch::IntegrationTestを継承したテストを作成することで、urlにリクエストを送って挙動を確認するテストを実施できます。

書き方の例のコア部分は以下のようになります。

class SomeControllerTest < ActionDispatch::IntegrationTest
  test "should get users list" do
    get '/users/index'
    assert_response :success
    assert_select 'table#users'
  end
end
  • getメソッドにパスを指定してリクエストする
    • ほかにもpostpatchメソッドなどHTTPリクエストに対応するメソッドがある
    • 第2引数にリクエストパラメータを指定できる
    • HTTPリクエストの詳細なカスタマイズについては、 ActionDispatch::Integration::RequestHelpersのAPIdoc参照
  • assert_responseメソッドでHTTPステータスを判定する
    • リダイレクトを追跡するメソッドもある
    • assert_redirected_toを使うと次ステップのpathもチェックできる
  • assert_selectでViewに存在する特徴的なタグをチェックできる
    • CSSクエリセレクタと同様の書き方で指定
  • メール送信もテスト可能

結合テストはある状態(setupメソッドで再現)とパラメータを用いてリクエストした際の外形的な挙動を観察します。インスタンス変数など値のチェックは実施しません。
ログインセッションを再現する例などの応用については、 IntegrationtestクラスのAPIdocに解説があります。

値のチェックは別途ユニットテストに書きますが、ユニットテストはよくあるテストの書き方とさして違わず、特筆すべき点はありません。

また、結合テストと似たようなテスト手法にシステムテストがあります。これはブラウザの操作をシミュレートするものです。ユーザー操作に最も近いテストではありますが、HTML構造に依存した書き方が必要となるため、やや低レベルのテストコードを書く必要があります。

Write系の確認方法

外形的なテストでWrite系の処理を確認する場合、以下のように1件増えていることのアサーションでチェックすると良いでしょう。
また、POSTリクエストもparamsオプションでモデルのパラメータを送信でき、異常な値のケースや攻撃のシミュレートもできます。

class SomeControllerTest < ActionDispatch::IntegrationTest
  test "should create new user" do
    assert_difference('User.count', 1) do
      post '/users', params: { name: "Julius Caesar" }
    end
    assert_response :redirect
  end
end

同様にdeleteも1件減っていることをチェックすると正常系をテストできます。

メールの確認

ActionMailerで送信するメール処理についても、以下のような方法でテスト可能です。

  • deliver_nowで送信しているメールについては、assert_difference('ActionMailer::Base.deliveries.size', +1)で送信件数の増加をカウントすることにより送信挙動を確認できる
  • deliver_laterで送信しているメールについては、assert_enqueued_emails(1)でキューをカウントすることで送信挙動を確認できる
  • 配信したメールは、ActionMailer::Base.deliveries.lastで取り出せる。subjecttoの内容を確認する
  • deliver_laterの非同期処理は、perform_enqueued_jobsを実行した後であれば、同期送信のメールと同様に扱える

テストダブル

スタブなどのテストダブルについては、Minitestで書ける範囲に制約があるため、gemを追加利用した方が無難でしょう。
mochaを利用する書き方を Rubyのテストにスタブを実装するで解説しています。

外部のAPIを呼び出しているような処理では、テストケースのsetupでスタブを実装するのが自然でしょう。

インスタンス変数のチェック

インテグーションテストでは変数のテストは非推奨であり、assert_selectでViewのレンダリング結果をテストするなど、より外形的な結果を確認すべきです。
ただし、リクエスト後にControllerオブジェクトを取得できるため、RubyのObject#instance_variable_getで以下のようにインスタンス変数を取得できます。

@controller.instance_variable_get('@data')
中馬崇尋
Chuma Takahiro