本ガイドは、アプリケーションをテストするためにRailsに組み込まれているメカニズムについて解説します。
このガイドの内容:
Railsでは、テストをきわめて簡単に作成できます。テストの作成は、モデルやコントローラを作成する時点でテストコードのスケルトンを作成することから始まります。
Railsのテストが作成されていれば、後はそれを実行するだけで、特に大規模なリファクタリングを行なう際にコードが期待どおりに動作していることを即座に確認できます。
Railsのテストはブラウザのリクエストをシミュレートできるので、ブラウザを手動で操作せずにアプリケーションのレスポンスをテストできます。
テスティングのサポートは最初期からRailsに組み込まれています。決して、最近テスティングが流行っていてクールだから導入してみた、というようなその場の思い付きで導入されたものではありません。
rails new
application_nameでRailsアプリケーションを作成すると、その場でtest
ディレクトリが作成されます。このディレクトリの内容は次のようになっています。
$ ls -F test application_system_test_case.rb controllers/ helpers/ mailers/ system/ channels/ fixtures/ integration/ models/ test_helper.rb
helpers
ディレクトリにはビューヘルパーのテスト、mailers
ディレクトリにはメーラーのテスト、models
ディレクトリにはモデル用のテストをそれぞれ保存します。channels
ディレクトリはAction Cableのコネクションやチャネルのテストを置きます。controllers
ディレクトリはコントローラ/ルーティング/ビューをまとめたテストの置き場所です。integration
ディレクトリはコントローラ同士のやりとりのテストを置く場所です。
システムテストのディレクトリ(system
)にはシステムテストを保存します。システムテストは、ユーザーエクスペリエンスに沿ったアプリケーションのテストを行うためのもので、JavaScriptのテストにも有用です。
システムテストはCapybaraから継承した機能で、アプリケーションのブラウザテストを実行します。
フィクスチャはテストデータを編成する方法の1つであり、fixtures
フォルダに置かれます。
ジョブに関連するテストが最初に作成されると、jobs
ディレクトリも作成されます。
test_helper.rb
にはテスティングのデフォルト設定を記入します。
システムテストのデフォルトの設定はapplication_system_test_case.rb
に保存されます。
デフォルトでは、すべてのRailsアプリケーションにはdevelopment、test、productionの3つの環境があります。
それぞれの環境の設定はいずれも同様の方法で変更できます。テストの場合は、config/environments/test.rb
にあるオプションを変更することでtest環境を変更できます。
テストはRAILS_ENV=test
環境で実行されます。
ガイドのRails をはじめようでrails generate model
コマンドを実行したのを覚えていますか。最初のモデルを作成すると、test
ディレクトリにはテストのスタブ(stub)が生成されます。
$ bin/rails generate model article title:string body:text ... create app/models/article.rb create test/models/article_test.rb create test/fixtures/articles.yml ...
test/models/article_test.rb
に含まれるデフォルトのテストスタブの内容は以下のような感じになります。
require "test_helper" class ArticleTest < ActiveSupport::TestCase # test "the truth" do # assert true # end end
Railsにおけるテスティングコードと用語に親しんでいただくために、このファイルに含まれている内容を上から順にご紹介します。
require "test_helper"
このファイルをrequire
すると、テストで使うデフォルト設定としてtest_helper.rb
が読み込まれます。今後書くすべてのテストにもこれを記述しますので、このファイルに追加したメソッドはテスト全体で使えるようになります。
class ArticleTest < ActiveSupport::TestCase # ... end
ArticleTest
クラスはActiveSupport::TestCase
を継承することによって、テストケース(test case)を1つ定義しています。これにより、ActiveSupport::TestCase
のすべてのメソッドをArticleTest
で利用できます。これらのメソッドのいくつかについては後述します。
ActiveSupport::TestCase
のスーパークラスはMinitest::Test
です。このMinitest::Test
を継承したクラスで定義される、test_
で始まるすべてのメソッドは単に「テスト」と呼ばれます。このtest_
は小文字でなければなりません。従って、test_password
およびtest_valid_password
と定義されたメソッド名は正式なテスト名となり、テストケースの実行時に自動的に実行されます。
Railsは、ブロックとテスト名をそれぞれ1つずつ持つtest
メソッドを1つ追加します。この時生成されるのは通常のMinitest::Unit
テストであり、メソッド名の先頭にtest_
が付きます。これにより、メソッドの命名に気を遣わずに済み、次のような感じで書けます。
test "the truth" do assert true end
上のコードは、以下のように書いた場合と同等に振る舞います。
def test_the_truth assert true end
通常のメソッド定義を使ってもよいのですが、test
マクロを適用することで、引用符で囲まれた読みやすいテスト名がテストメソッドの定義に変換されます。
テスト名からのメソッド名生成は、スペースをアンダースコアに置き換えることによって行われます。生成されたメソッド名はRubyの正規な識別子である必要はありません。テスト名にパンクチュエーション(句読点)などの文字が含まれていても大丈夫です。これが可能なのは、Rubyではメソッド名にどんな文字列でも使えるようになっているからです。普通でない文字を使おうとするとdefine_method
呼び出しやsend
呼び出しが必要になりますが、名前の付け方そのものには公式な制限はありません。
次に、最初のアサーションを見てみましょう。
assert true
アサーション(assertion: 主張)とは、オブジェクトまたは式を評価して、期待された結果が得られるかどうかをチェックするコードです。アサーションでは以下のようなチェックを行なうことができます。
1つのテストにはアサーションが1つ以上含まれることがあります。すべてのアサーションに成功してはじめてテストがパスします。
今度はテストが失敗した場合の結果を見てみましょう。そのためには、article_test.rb
テストケースに、確実に失敗するテストを以下のように追加してみます。
test "should not save article without title" do article = Article.new assert_not article.save end
それでは、新しく追加したテストを実行してみましょう(6
はテストが定義されている行番号です)。
$ bin/rails test test/models/article_test.rb:6 Run options: --seed 44656 # Running: F Failure: ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]: Expected true to be nil or false rails test test/models/article_test.rb:6 Finished in 0.023918s, 41.8090 runs/s, 41.8090 assertions/s. 1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
出力に含まれている単独の文字F
は失敗を表します。Failure
の後にこの失敗に対応するトレースが、失敗したテスト名とともに表示されています。次の数行はスタックトレースで、アサーションの実際の値と期待されていた値がその後に表示されています。デフォルトのアサーションメッセージには、エラー箇所を特定するのに十分な情報が含まれています。すべてのアサーションは失敗時のメッセージをさらに読みやすくするために、以下のようにメッセージをオプションパラメータとして渡せます。
test "should not save article without title" do article = Article.new assert_not article.save, "Saved the article without a title" end
テストを実行すると、以下のようにさらに読みやすいメッセージが表示されます。
Failure: ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]: Saved the article without a title
今度はtitleフィールドに対してモデルのレベルでバリデーションを行い、テストがパスするようにしてみましょう。
class Article < ApplicationRecord validates :title, presence: true end
このテストはパスするはずです。もう一度テストを実行してみましょう。
$ bin/rails test test/models/article_test.rb:6 Run options: --seed 31252 # Running: . Finished in 0.027476s, 36.3952 runs/s, 36.3952 assertions/s. 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
お気付きになった方もいるかと思いますが、私たちは欲しい機能が未実装であるために、あえて失敗するテストを最初に作成していることにご注目ください。続いてその機能を実装し、それからもう一度実行してテストがパスすることを確認しました。ソフトウェア開発の世界ではこのようなアプローチをテスト駆動開発(Test-Driven Development : TDD)と呼んでいます。
エラーがどのように表示されるかを確認するために、以下のようなエラーを含んだテストを作ってみましょう。
test "should report error" do # some_undefined_variable is not defined elsewhere in the test case some_undefined_variable assert true end
これで、このテストを実行するとさらに多くのメッセージがコンソールに表示されるようになりました。
$ bin/rails test test/models/article_test.rb Run options: --seed 1808 # Running: .E Error: ArticleTest#test_should_report_error: NameError: undefined local variable or method 'some_undefined_variable' for #<ArticleTest:0x007fee3aa71798> test/models/article_test.rb:11:in 'block in <class:ArticleTest>' rails test test/models/article_test.rb:9 Finished in 0.040609s, 49.2500 runs/s, 24.6250 assertions/s. 2 runs, 1 assertions, 0 failures, 1 errors, 0 skips
今度は'E'が出力されます。これはエラーが発生したテストが1つあることを示しています。
テストスイートに含まれる各テストメソッドは、エラーまたはアサーション失敗が発生するとそこで実行を中止し、次のメソッドに進みます。テストメソッドの実行順序はすべてランダムです。テストの実行順序はconfig.active_support.test_order
オプションで設定できます。
テストが失敗すると、それに応じたバックトレースが出力されます。Railsはデフォルトでバックトレースをフィルタし、アプリケーションに関連するバックトレースのみを出力します。これによって、フレームワークから発生する不要な情報を排除して作成中のコードに集中できます。完全なバックトレースを参照しなければならなくなった場合は、-b
(または--backtrace
)引数を設定するだけで動作を変更できます。
$ bin/rails test -b test/models/article_test.rb
このテストをパスさせるには、assert_raises
を用いて以下のようにテストを変更します。
test "should report error" do # some_undefined_variableはテストケースのどこにも定義されていない assert_raises(NameError) do some_undefined_variable end end
これでテストはパスするはずです。
ここまでにいくつかのアサーションをご紹介しましたが、これらはごく一部に過ぎません。アサーションこそは、テストの中心を担う重要な存在です。システムが計画通りに動作していることを実際に確認しているのはアサーションです。
アサーションは非常に多くの種類が使えるようになっています。
以下で紹介するのは、Minitest
で使えるアサーションからの抜粋です。MinitestはRailsにデフォルトで組み込まれているテスティングライブラリです。[msg]
パラメータは1個のオプション文字列メッセージであり、テストが失敗したときのメッセージをわかりやすくするにはここで指定します(必須ではありません)。
assert( test, [msg] )
test
はtrueであると主張する。assert_not( test, [msg] )
test
はfalseであると主張する。assert_equal( expected, actual, [msg] )
expected == actual
はtrueであると主張する。assert_not_equal( expected, actual, [msg] )
expected != actual
はtrueであると主張する。assert_same( expected, actual, [msg] )
expected.equal?(actual)
はtrueであると主張する。assert_not_same( expected, actual, [msg] )
expected.equal?(actual)
はfalseであると主張する。assert_nil( obj, [msg] )
obj.nil?
はtrueであると主張する。assert_not_nil( obj, [msg] )
obj.nil?
はfalseであると主張する。assert_empty( obj, [msg] )
obj
はempty?
であると主張する。assert_not_empty( obj, [msg] )
obj
はempty?
ではないと主張する。assert_match( regexp, string, [msg] )
assert_no_match( regexp, string, [msg] )
assert_includes( collection, obj, [msg] )
obj
はcollection
に含まれると主張する。assert_not_includes( collection, obj, [msg] )
obj
はcollection
に含まれないと主張する。assert_in_delta( expected, actual, [delta], [msg] )
expected
とactual
の個数の差はdelta
以内であると主張する。assert_not_in_delta( expected, actual, [delta], [msg] )
expected
とactual
の個数の差はdelta
以内にはないと主張する。assert_in_epsilon ( expected, actual, [epsilon], [msg] )
expected
とactual
の個数の差がepsilon
より小さいと主張する。assert_not_in_epsilon ( expected, actual, [epsilon], [msg] )
expected
とactual
の数値にはepsilon
より小さい相対誤差がないと主張する。assert_throws( symbol, [msg] ) { block }
assert_raises( exception1, exception2, ... ) { block }
assert_instance_of( class, obj, [msg] )
obj
はclass
のインスタンスであると主張する。assert_not_instance_of( class, obj, [msg] )
obj
はclass
のインスタンスではないと主張する。assert_kind_of( class, obj, [msg] )
obj
はclass
またはそのサブクラスのインスタンスであると主張する。assert_not_kind_of( class, obj, [msg] )
obj
はclass
またはそのサブクラスのインスタンスではないと主張する。assert_respond_to( obj, symbol, [msg] )
obj
はsymbol
に応答すると主張する。assert_not_respond_to( obj, symbol, [msg] )
obj
はsymbol
に応答しないと主張する。assert_operator( obj1, operator, [obj2], [msg] )
obj1.operator(obj2)
はtrueであると主張する。assert_not_operator( obj1, operator, [obj2], [msg] )
obj1.operator(obj2)
はfalseであると主張する。assert_predicate ( obj, predicate, [msg] )
obj.predicate
はtrueであると主張する(例:assert_predicate str, :empty?
)。assert_not_predicate ( obj, predicate, [msg] )
obj.predicate
はfalseであると主張する(例:assert_not_predicate str, :empty?
)。assert_error_reported(class) { block }
assert_error_reported IOError { Rails.error.report(IOError.new("Oops")) }
)。assert_no_error_reported { block }
assert_no_error_reported { perform_service }
)flunk( [msg] )
これらはMinitestがサポートするアサーションの一部に過ぎません。最新の完全なアサーションのリストについてはMinitest APIドキュメント、特にMinitest::Assertions
を参照してください。
テスティングフレームワークはモジュール化されているので、アサーションを自作して利用することもできます。実際、Railsはまさにそれを行っているのです。Railsには開発を楽にしてくれる特殊なアサーションがいくつも追加されています。
アサーションの自作は高度なトピックなので、このチュートリアルでは扱いません。
Railsはminitest
フレームワークに以下のような独自のカスタムアサーションを追加しています。
assert_difference(expressions, difference = 1, message = nil)
yield
されたブロックで評価された結果である式の戻り値における数値の違いをテストする。assert_no_difference(expressions, message = nil, &block)
assert_changes(expressions, message = nil, from:, to:, &block)
assert_no_changes(expressions, message = nil, &block)
assert_nothing_raised { block }
assert_recognizes(expected_options, path, extras={}, message=nil)
expected_options
ハッシュで渡された(解析オプションがパスと一致したことを主張する。基本的にこのアサーションでは、Railsがexpected_options
で渡されたルーティングを認識していると主張する。assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
assert_recognizes
と逆の動作)。extras
パラメータは、クエリ文字列に追加リクエストがある場合にそのパラメータの名前と値をリクエストに渡すのに使われる。message
パラメータにはアサーションが失敗した場合のカスタムエラーメッセージを渡せる。assert_response(type, message = nil)
:success
を指定するとステータスコード200〜299を指定したことになり、同様に:redirect
は300〜399、:missing
は404、:error
は500〜599にそれぞれマッチする。ステータスコードの数字や同等のシンボルを直接渡すこともできる。詳細についてはステータスコードの完全なリストおよびシンボルとステータスコードの対応リストを参照。assert_redirected_to(options = {}, message=nil)
assert_redirected_to root_path
などの名前付きルートを渡すことも、assert_redirected_to @article
などのActive Recordオブジェクトを渡すことも可能。assert_queries_count(count = nil, include_schema: false, &block)
&block
がSQLクエリのint
数値を生成することを主張する。assert_no_queries(include_schema: false, &block)
&block
がSQLクエリを生成しないことを主張する。assert_queries_match(pattern, count: nil, include_schema: false, &block)
&block
が指定のパターンにマッチするSQLクエリを生成することを主張する。assert_no_queries_match(pattern, &block)
&block
が指定のパターンにマッチするSQLクエリを生成しないことを主張する。これらのアサーションのいくつかについては次の章で説明します。
Minitest::Assertions
に定義されているassert_equal
などの基本的なアサーションは、あらゆるテストケース内で用いられているクラスで利用できます。実際には、以下から継承したクラスもRailsで利用できます。
ActiveSupport::TestCase
ActionMailer::TestCase
ActionView::TestCase
ActiveJob::TestCase
ActionDispatch::IntegrationTest
ActionDispatch::SystemTestCase
Rails::Generators::TestCase
各クラスにはMinitest::Assertions
が含まれているので、どのテストでも基本的なアサーションを利用できます。
Minitest
について詳しくは、Minitestのドキュメントを参照してください。
デフォルトのRailsは、テストをデータベーストランザクションに自動的にラップし、テスト完了後にロールバックします。これにより、テストは互いに独立し、データベースへの変更は1つのテスト内でのみ表示されます。
class MyTest < ActiveSupport::TestCase test "newly created users are active by default" do # このテストは暗黙でデータベーストランザクションにラップされるので、 # ここで作成したユーザーは他のテストからは見えない assert User.create.active? end end
ただし、ActiveRecord::Base.current_transaction
は引き続き意図したとおりに動作します。
class MyTest < ActiveSupport::TestCase test "current_transaction" do # テストを暗黙のトランザクションが囲んでいても、 # current_transactionのアプリケーションレベルの意味論に影響しない assert User.current_transaction.blank? end end
複数の書き込みデータベースが存在する場合、テストはそれらに対応する多数のトランザクションにラップされ、すべてがロールバックします。
以下のようにすることで、個別のテストケースでトランザクションを無効にできます。
class MyTest < ActiveSupport::TestCase # このテストケースではテストをデータベーストランザクションで暗黙にラップしなくなる self.use_transactional_tests = false end
bin/rails test
コマンドを使ってすべてのテストを一括実行できます。
個別のテストファイルを実行するには、bin/rails test
コマンドにそのテストケースを含むファイル名を渡します。
$ bin/rails test test/models/article_test.rb Run options: --seed 1559 # Running: .. Finished in 0.027034s, 73.9810 runs/s, 110.9715 assertions/s. 2 runs, 3 assertions, 0 failures, 0 errors, 0 skips
上を実行すると、そのテストケースに含まれるメソッドがすべて実行されます。
あるテストケースの特定のテストメソッドだけを実行するには、-n
(または--name
)フラグでテストのメソッド名を指定します。
$ bin/rails test test/models/article_test.rb -n test_the_truth Run options: -n test_the_truth --seed 43583 # Running: . Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
行番号を指定すると、特定の行だけをテストできます。
$ bin/rails test test/models/article_test.rb:6 # 特定のテストの特定行のみをテスト
行を範囲指定することで、特定の範囲のテストを実行することも可能です。
$ bin/rails test test/models/article_test.rb:6-20 # 6行目から20行目までテストを実行
ディレクトリを指定すると、そのディレクトリ内のすべてのテストを実行できます。
$ bin/rails test test/controllers # 指定ディレクトリのテストをすべて実行
テストランナーではこの他にも、「failing fast」やテスト終了時に必ずテストを出力するといったさまざまな機能が使えます。次を実行してテストランナーのドキュメントをチェックしてみましょう。
$ bin/rails test -h Usage: bin/rails test [PATHS...] Run tests except system tests Examples: You can run a single test by appending a line number to a filename: bin/rails test test/models/user_test.rb:27 You can run multiple tests with in a line range by appending the line range to a filename: bin/rails test test/models/user_test.rb:10-20 You can run multiple files and directories at the same time: bin/rails test test/controllers test/integration/login_test.rb By default test failures and errors are reported inline during a run. minitest options: -h, --help Display this help. --no-plugins Bypass minitest plugin auto-loading (or set $MT_NO_PLUGINS). -s, --seed SEED Sets random seed. Also via env. Eg: SEED=n rake -v, --verbose Verbose. Show progress processing files. -q, --quiet Quiet. Show no progress processing files. --show-skips Show skipped at the end of run. -n, --name PATTERN Filter run on /regexp/ or string. --exclude PATTERN Exclude /regexp/ or string from run. -S, --skip CODES Skip reporting of certain types of results (eg E). Known extensions: rails, pride -w, --warnings Run with Ruby warnings enabled -e, --environment ENV Run tests in the ENV environment -b, --backtrace Show the complete backtrace -d, --defer-output Output test failures and errors after the test run -f, --fail-fast Abort test run on first failure or error -c, --[no-]color Enable color in the output --profile [COUNT] Enable profiling of tests and list the slowest test cases (default: 10) -p, --pride Pride. Show your testing pride!
CI(Continuous Integration)環境ですべてのテストを実行するのに必要なコマンドは、以下の1つだけです。
$ bin/rails test
システムテストを利用している場合、動作が遅いためbin/rails test
ではシステムテストを実行しません。システムテストを実行するには、bin/rails test:system
を実行するCIステップを追加するか、最初のステップをbin/rails test:all
に変更して、システムテストを含むすべてのテストを実行するようにします。
並列テスト(parallel testing)を用いてテストスイートを並列実行できます。デフォルトの手法はプロセスのforkですが、スレッディングもサポートされています。テストを並列に実行することで、テストスイート全体の実行に要する時間を削減できます。
デフォルトの並列化手法は、RubyのDRbシステムを用いるプロセスのforkです。プロセスは、提供されるワーカー数に基づいてforkされます。デフォルトの数値は、実行されるマシンの実際のコア数ですが、parallelize
メソッドに数値を渡すことで変更できます。
並列化を有効にするには、test_helper.rb
に以下を記述します。
class ActiveSupport::TestCase parallelize(workers: 2) end
渡されたワーカー数は、プロセスがfork
される回数です。ローカルテストスイートをCIとは別の方法で並列化したい場合は、以下の環境変数を用いてテスト実行時に使うべきワーカー数を簡単に変更できます。
$ PARALLEL_WORKERS=15 bin/rails test
テストを並列化すると、Active Recordはデータベースの作成やスキーマのデータベースへの読み込みを自動的にプロセスごとに扱います。データベース名の後ろには、ワーカー数に応じた数値が追加されます。たとえば、ワーカーが2つの場合はtest-database-0
とtest-database-1
がそれぞれ作成されます。
渡されたワーカー数が1以下の場合はプロセスはforkされず、テストは並列化しません。テストのデータベースもオリジナルのtest-database
が使われます。
2つのフックが提供されます。1つはプロセスがforkされるときに実行され、1つはforkしたプロセスがcloseする直前に実行されます。これらのフックは、データベースを複数使っている場合や、ワーカー数に応じた他のタスクを実行する場合に便利です。
parallelize_setup
メソッドは、プロセスがforkした直後に呼び出されます。parallelize_teardown
メソッドは、プロセスがcloseする直前に呼び出されます。
class ActiveSupport::TestCase parallelize_setup do |worker| # データベースをセットアップする end parallelize_teardown do |worker| # データベースをクリーンアップする end parallelize(workers: :number_of_processors) end
これらのメソッドは、スレッドを用いる並列テストでは不要であり、利用できません。
スレッドを使いたい場合やJRubyを利用する場合のために、スレッドによる並列化オプションも提供されています。スレッドによる並列化の背後には、MinitestのParallel::Executor
があります。
並列化手法をforkからスレッドに変更するには、test_helper.rb
に以下を記述します。
class ActiveSupport::TestCase parallelize(workers: :number_of_processors, with: :threads) end
JRubyで生成されたRailsアプリケーションには、自動的にwith: :threads
オプションが含まれます。
parallelize
に渡されたワーカー数は、テストで使うスレッド数を決定します。ローカルテストスイートをCIとは別の方法で並列化したい場合は、以下の環境変数を用いてテスト実行時に使うべきワーカー数を簡単に変更できます。
$ PARALLEL_WORKERS=15 bin/rails test
並列トランザクションをスレッドで実行するコードをテストしたい場合、テスト用トランザクションの下にすでにネストされているため、トランザクションが互いにブロックしてしまう可能性があります。
これを回避するには、以下のようにself.use_transactional_tests = false
を設定することで、テストケースのクラスでトランザクションを無効にできます。
class WorkerTest < ActiveSupport::TestCase self.use_transactional_tests = false test "parallel transactions" do # トランザクションを作成するスレッドがいくつか起動される end end
トランザクションを無効にしたテストでは、テストが完了しても変更が自動でロールバックされなくなります。そのため、テストで作成されたデータをすべてクリーンアップする必要があります。
テストを並列実行すると、データベースのセットアップやフィクスチャの読み込みなどでオーバーヘッドが発生します。そのため、Railsはテスト数が50未満の場合は並列化を行いません。
このスレッショルド(閾値)はtest.rb
で以下のように設定できます。
config.active_support.test_parallelization_threshold = 100
以下のようにテストケースレベルでも並列化のスレッショルドを設定可能です。
class ActiveSupport::TestCase parallelize threshold: 100 end
Railsアプリケーションは、ほぼ間違いなくデータベースと密接なやりとりを行いますので、テスティングにもデータベースが必要となります。効率のよいテストを作成するには、データベースの設定方法とサンプルデータの導入方法を理解しておく必要があります。
デフォルトでは、すべてのRailsアプリケーションにはdevelopment、test、productionの3つの環境があります。それぞれの環境におけるデータベース設定はconfig/database.yml
で行います。
テスティング専用のデータベースを用いることで、それを設定して他の環境から切り離された専用のテストデータにアクセスできます。これにより、development環境やproduction環境のデータベースにあるデータを気にすることなく確実なテストを実行できます。
テストを実行するには、テストデータベースが最新の状態で構成されている必要があります。テストヘルパーは、テストデータベースに未完了のマイグレーションが残っていないかどうかをチェックします。マイグレーションがすべて終わっている場合、db/schema.rb
やdb/structure.sql
をテストデータベースに読み込みます。ペンディングされたマイグレーションがある場合、エラーが発生します。このエラーが発生するということは、スキーマのマイグレーションが不完全であることを意味します。developmentデータベースに対してマイグレーション(bin/rails db:migrate
)を実行することで、スキーマが最新の状態になります。
既存のマイグレーションに変更が加えられていると、テストデータベースを再構築する必要があります。bin/rails test:db
を実行することで再構築できます。
よいテストを作成するにはよいテストデータを準備する必要があることを理解しておく必要があります。 Railsでは、テストデータの定義とカスタマイズはフィクスチャで行うことができます。 網羅的なドキュメントについては、フィクスチャAPIドキュメントを参照してください。
フィクスチャ(fixture)とは、いわゆるサンプルデータを言い換えたものです。フィクスチャを使うことで、事前に定義したデータをテスト実行直前にtestデータベースに導入することができます。フィクスチャはYAMLで記述され、特定のデータベースに依存しません。1つのモデルにつき1つのフィクスチャファイルが作成されます。
フィクスチャは、テストで必要なありとあらゆるオブジェクトを作成できる設計ではありません。フィクスチャを最適に管理するには、よくあるテストケースに適用可能なデフォルトデータのみを用いることです。
フィクスチャファイルはtest/fixtures
の下に置かれます。rails generate model
を実行してモデルを新規作成すると、モデルのフィクスチャスタブが自動的に作成され、このディレクトリに置かれます。
YAML形式のフィクスチャは人間にとってとても読みやすく、サンプルデータを容易に記述できます。この形式のフィクスチャには.ymlというファイル拡張子が与えられます(users.yml
など)。
YAMLフィクスチャファイルのサンプルを以下に示します。
# この行はYAMLのコメントである david: name: David Heinemeier Hansson birthday: 1979-10-15 profession: Systems development steve: name: Steve Ross Kellock birthday: 1974-09-27 profession: guy with keyboard
各フィクスチャはフィクスチャ名とコロンで始まり、その後にコロンで区切られたキー/値ペアのリストがインデント付きで置かれます。通常、レコード間は空行で区切られます。行の先頭に#
文字を置くことで、フィクスチャファイルにコメントを追加できます。'yes'や'no'などのYAMLキーワードに似たキーについては、引用符で囲むことでYAMLパーサーが正常に動作できます。
関連付けを使っている場合は、2つの異なるフィクスチャの間に参照ノードを1つ定義すれば済みます。belongs_to
とhas_many
関連付けの例を以下に示します。
# test/fixtures/categories.yml about: name: About
# test/fixtures/articles.yml first: title: Welcome to Rails! category: about
# test/fixtures/action_text/rich_texts.yml first_content: record: first (Article) name: content body: <div>Hello, from <strong>a fixture</strong></div>
fixtures/articles.yml
にある記事first
のcategory
キーの値がabout
になり、fixtures/action_text/rich_texts.yml
にあるfirst_content
エントリのrecord
キーの値がfirst (Article)
になっている点にもご注目ください。これは、前者についてはActive Recordがfixtures/categories.yml
にあるカテゴリabout
を読み込むように、後者についてはAction Textがfixtures/articles.yml
にある記事first
を読み込むようにヒントを与えています。
関連付けが名前で互いを参照している場合、関連付けられたフィクスチャにあるid:
属性を指定する代わりに、フィクスチャ名を使えます。Railsはテストの実行中に、自動的に主キーを割り当てて一貫性を保ちます。関連付けの詳しい動作については、フィクスチャAPIドキュメントを参照してください。
Active Recordの他のモデルと同様、Active Storageの添付ファイルレコードはActiveRecord::Base
インスタンスを継承しているのでフィクスチャに入れられます。
thumbnail
添付画像に関連付けられているArticle
モデルのフィクスチャデータYAMLを考えてみましょう。
class Article < ApplicationRecord has_one_attached :thumbnail end
# test/fixtures/articles.yml first: title: An Article
image/png
エンコードされたファイルがtest/fixtures/files/first.png
にあると仮定します。このとき以下のYAMLフィクスチャエントリは、関連するActiveStorage::Blob
とActiveStorage::Attachment
レコードを生成します。
# test/fixtures/active_storage/blobs.yml first_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob filename: "first.png" %>
# test/fixtures/active_storage/attachments.yml first_thumbnail_attachment: name: thumbnail record: first (Article) blob: first_thumbnail_blob
ERBは、テンプレート内にRubyコードを埋め込むのに使われます。YAMLフィクスチャ形式のファイルは、Railsに読み込まれたときにERBによる事前処理が行われます。ERBを活用すれば、Rubyで一部のサンプルデータを生成できます。たとえば、以下のコードを使えば1000人のユーザーを生成できます。
<% 1000.times do |n| %> user_<%= n %>: username: <%= "user#{n}" %> email: <%= "user#{n}@example.com" %> <% end %>
Railsはデフォルトで、test/fixtures
フォルダにあるすべてのフィクスチャを自動的に読み込み、モデルやコントローラのテストで利用します。フィクスチャの読み込みは主に以下の3つの手順からなります。
Railsでは、データベースから既存のデータベースを削除するために外部キーやチェック制約といった参照整合性(referential integrity)トリガーを無効にしようとします。テスト実行時のパーミッションエラーが発生して困っている場合は、test環境のデータベースユーザーがこれらのトリガーを無効にする特権を持っていることをご確認ください(PostgreSQLの場合、すべてのトリガーを無効にできるのはsuperuserのみです。PostgreSQLのパーミッションについて詳しくはこちらの記事を参照してください)。
フィクスチャは、実はActive Recordのインスタンスです。前述の手順3にもあるように、フィクスチャはスコープがテストケースのローカルになっているメソッドを自動的に利用可能にしてくれるので、フィクスチャのオブジェクトに直接アクセスできます。以下に例を示します。
# davidという名前のフィクスチャに対応するUserオブジェクトを返す users(:david) # idで呼び出されたdavidのプロパティを返す users(:david).id # Userクラスで利用可能なメソッドにアクセスすることもできる david = users(:david) david.call(david.partner)
複数のフィクスチャを一括で取得するには、次のようにフィクスチャ名をリストで渡します。
# davidとsteveというフィクスチャを含む配列を返す users(:david, :steve)
モデルのテストは、アプリケーションのさまざまなモデルをテストするのに使われます。
Railsのモデルテストはtest/models
ディレクトリの下に保存されます。Railsではモデルテストのスケルトンを生成するジェネレータが提供されています。
$ bin/rails generate test_unit:model article title:string body:text create test/models/article_test.rb create test/fixtures/articles.yml
モデルテストにはActionMailer::TestCase
のような独自のスーパークラスがなく、代わりにActiveSupport::TestCase
を継承します。
システムテストはアプリケーションのユーザー操作のテストに使えます。テストは、実際のブラウザまたはヘッドレスブラウザに対して実行されます。システムテストではそのために背後でCapybaraを使います。
アプリケーションのtest/system
ディレクトリは、Railsのシステムテストを作成するために使います。Railsではシステムテストのスケルトンを生成するジェネレータが提供されています。
$ bin/rails generate system_test users invoke test_unit create test/system/users_test.rb
生成直後のシステムテストは次のようになっています。
require "application_system_test_case" class UsersTest < ApplicationSystemTestCase # test "visiting the index" do # visit users_url # # assert_selector "h1", text: "Users" # end end
システムテストでは、デフォルトでSeleniumドライバと画面サイズ1400x1400のChromeブラウザを用いて実行されます。次のセクションで、デフォルト設定の変更方法について説明します。
デフォルトのRailsは、テスト中に発生した例外のrescue
を試み、HTMLエラーページで応答します。この振る舞いはconfig.action_dispatch.show_exceptions
設定で制御できます。
Railsのシステムテストのデフォルト設定変更方法は非常にシンプルです。すべての設定が抽象化されているので、テストの作成に集中できます。
新しいアプリケーションやscaffoldを生成すると、そのテストのディレクトリにapplication_system_test_case.rb
ファイルが作成されます。システムテストの設定はすべてここで行います。
デフォルト設定を変更したい場合は、システムテストのdriven_by
項目を変更できます。たとえばドライバをSeleniumからCupriteに変更する場合は、まずcuprite
gemをGemfileに追加し、続いてapplication_system_test_case.rb
を以下のように変更します。
require "test_helper" require "capybara/cuprite" class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :cuprite end
このドライバ名はdriven_by
で必要な引数です。driven_by
に渡せるオプション引数としては、他に:using
(ブラウザを指定する、Seleniumでのみ有効)や:screen_size
(スクリーンショットのサイズ変更)、:options
(ドライバでサポートされるオプションの指定)があります。
require "test_helper" class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :firefox end
ヘッドレスブラウザを使いたい場合は、:using
引数にheadless_chrome
またはheadless_firefox
を追加してヘッドレスChromeやヘッドレスFirefoxを利用できます。
require "test_helper" class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :headless_chrome end
DockerのヘッドレスChromeなどのリモートブラウザを使いたい場合は、options
でbrowser
にリモートurl
を追加する必要があります。
require "test_helper" class ApplicationSystemTestCase < ActionDispatch::SystemTestCase url = ENV.fetch("SELENIUM_REMOTE_URL", nil) options = if url { browser: :remote, url: url } else { browser: :chrome } end driven_by :selenium, using: :headless_chrome, options: options end
これで、以下を実行すればリモートブラウザに接続されるはずです。
$ SELENIUM_REMOTE_URL=http://localhost:4444/wd/hub bin/rails test:system
テスト対象のアプリケーションがリモートでも動作している場合(Dockerコンテナなど)は、リモートサーバーの呼び出し方法に関する追加情報をCapybaraに渡す必要があります。
require "test_helper" class ApplicationSystemTestCase < ActionDispatch::SystemTestCase def setup Capybara.server_host = "0.0.0.0" # すべてのインターフェイスにバインドする Capybara.app_host = "http://#{IPSocket.getaddress(Socket.gethostname)}" if ENV["SELENIUM_REMOTE_URL"].present? super end # ... end
これで、DockerコンテナとCIのどちらで動作していても、リモートブラウザとサーバに接続できるようになったはずです。
Railsで提供されていないCapybara設定が必要な場合は、application_system_test_case.rb
ファイルに設定を追加できます。
追加設定についてはCapybaraのドキュメントを参照してください。
ScreenshotHelper
は、テスト中のスクリーンショットをキャプチャするよう設計されたヘルパーで、テストが失敗した時点のブラウザ画面を確認するときや、デバッグでスクリーンショットを確認するときに有用です。
take_screenshot
メソッドおよびtake_failed_screenshot
メソッドが提供されており、take_failed_screenshot
はRails内部のafter_teardown
に自動的に含まれます。
take_screenshot
ヘルパーメソッドはテストのどこにでも書くことができ、ブラウザのスクリーンショット撮影に使えます。
それではブログアプリケーションにシステムテストを追加することにしましょう。システムテストで最初にindexページを表示し、新しいブログ記事を1件作成します。
scaffoldジェネレータを使った場合はシステムテストのスケルトンが自動で作成されています。scaffoldジェネレータを使わなかった場合はシステムテストのスケルトンを自分で作成しておきましょう。
$ bin/rails generate system_test articles
上のコマンドを実行するとテストファイルのプレースホルダが作成され、次のように表示されるはずです。
invoke test_unit
create test/system/articles_test.rb
それではこのファイルを開いて最初のアサーションを書きましょう。
require "application_system_test_case" class ArticlesTest < ApplicationSystemTestCase test "viewing the index" do visit articles_path assert_selector "h1", text: "Articles" end end
このテストは記事のindexページにh1
が存在していればパスします。
システムテストを実行します。
bin/rails test:system
デフォルトではbin/rails test
を実行してもシステムテストが実行されません。実際にシステムテストを実行するにはbin/rails test:system
を実行してください。
それではブログで新しい記事を作成するフローをテストしましょう。
test "should create Article" do visit articles_path click_on "New Article" fill_in "Title", with: "Creating an Article" fill_in "Body", with: "Created this article successfully!" click_on "Create Article" assert_text "Creating an Article" end
最初の手順ではvisit articles_path
を呼び出し、記事のindexページの表示をテストします。
続いてclick_on "New Article"
でindexページの「New Article」ボタンを検索します。するとブラウザが/articles/new
にリダイレクトされます。
次に記事のタイトルと本文に指定のテキストを入力しています。フィールドへの入力が終わったら「Create Article」をクリックして、データベースに新しい記事を作成するPOSTリクエストを送信しています。
そして記事の元のindexページにリダイレクトされ、新しい記事のタイトルがその記事のindexページに表示されます。
デスクトップ用のテストに加えてモバイル用のサイズのテストも行いたい場合は、ActionDispatch::SystemTestCase
を継承した別のクラスを作成してテストスイートで利用します。この例では、/test
ディレクトリにmobile_system_test_case.rb
というファイルを作成し、以下のように設定しています。
require "test_helper" class MobileSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :chrome, screen_size: [375, 667] end
この設定を使うには、test/system
ディレクトリの下にMobileSystemTestCase
を継承したテストを作成します。
これで、さまざまな構成を使ってアプリをテストできるようになります。
require "mobile_system_test_case" class PostsTest < MobileSystemTestCase test "visiting the index" do visit posts_url assert_selector "h1", text: "Posts" end end
システムテストの長所は、ユーザーによるやり取りをコントローラやモデルやビューを用いてテストできるという点で結合テストに似ていますが、本物のユーザーが操作しているかのようにテストを実際に実行できるため、ずっと頑丈です。ユーザーがアプリケーションで行える操作であれば、コメント入力や記事の削除、ドラフト記事の公開など何でも行えます。
結合テスト(integration test、統合テストとも)は、複数のコントローラ同士のやりとりをテストします。一般に、アプリケーション内の重要なワークフローのテストに使われます。
Railsの結合テストは、アプリケーションのtest/integration
ディレクトリに作成します。Railsでは結合テストのスケルトンを生成するジェネレータが提供されています。
$ bin/rails generate integration_test user_flows exists test/integration/ create test/integration/user_flows_test.rb
生成直後の結合テストは以下のような内容になっています。
require 'test_helper' class UserFlowsTest < ActionDispatch::IntegrationTest # test "the truth" do # assert true # end end
結合テストはActionDispatch::IntegrationTest
から継承されます。これにより、結合テスト内でさまざまなヘルパーが利用できます。
デフォルトのRailsは、テスト中に発生した例外のrescue
を試み、HTMLエラーページで応答します。この振る舞いはconfig.action_dispatch.show_exceptions
設定で制御できます。
システムテストでは、標準のテストヘルパー以外にもActionDispatch::IntegrationTest
から継承されるいくつかのヘルパーを利用できます。その中から3つのカテゴリについて簡単にご紹介します。
結合テストランナーについてはActionDispatch::Integration::Runner
を参照してください。
リクエストの実行についてはActionDispatch::Integration::RequestHelpers
にあるヘルパーを用いることにします。
ファイルをアップロードする必要がある場合は、ActionDispatch::TestProcess::FixtureFile
を参照してください。
セッションを改変する必要がある場合や、結合テストのステートを変更する必要がある場合は、ActionDispatch::Integration::Session
を参照してください。
それではブログアプリケーションに結合テストを追加することにしましょう。最初に基本的なワークフローととして新しいブログ記事を1件作成し、すべて問題なく動作することを確認します。
まずは結合テストのスケルトンを生成します。
$ bin/rails generate integration_test blog_flow
上のコマンドを実行するとテストファイルのプレースホルダが作成され、次のように表示されるはずです。
invoke test_unit
create test/integration/blog_flow_test.rb
それではこのファイルを開いて最初のアサーションを書きましょう。
require "test_helper" class BlogFlowTest < ActionDispatch::IntegrationTest test "can see the welcome page" do get "/" assert_select "h1", "Welcome#index" end end
リクエストで生成されるHTMLをテストするassert_select
についてはこの後のビューをテストするで言及します。これはリクエストに対するレスポンスのテストに用いるもので、重要なHTML要素がコンテンツに存在するというアサーションを行います。
rootパスを表示すると、そのビューでwelcome/index.html.erb
が表示されるはずなので、このアサーションはパスするはずです。
ブログに新しい記事を1件作成して、生成された記事が表示できていることをテストしましょう。
test "can create an article" do get "/articles/new" assert_response :success post "/articles", params: { article: { title: "can create", body: "article successfully." } } assert_response :redirect follow_redirect! assert_response :success assert_select "p", "Title:\n can create" end
理解のため、このテストを細かく分けてみましょう。
最初にArticles
コントローラの:new
アクションを呼びます。このレスポンスは成功するはずです。
次にArticles
コントローラの:create
アクションにPOSTリクエストを送信します。
post "/articles", params: { article: { title: "can create", body: "article successfully." } } assert_response :redirect follow_redirect!
その次の2行では、記事が1件作成されるときのリクエストのリダイレクトを扱います。
リダイレクト実行後に続いて別のリクエストを行う予定がある場合は、follow_redirect!
を必ず呼び出してください。
最後は、レスポンスが成功して記事がページ上で読める状態になっているというアサーションです。
ブログを表示して記事を1件作成するという、きわめて小規模なワークフローを無事テストできました。このテストにコメントを追加することも、記事の削除や編集のテストを行うこともできます。結合テストは、アプリケーションのあらゆるユースケースに伴うエクスペリエンスのテストに向いています。
Railsで1つのコントローラに含まれる複数のアクションのテストを作成することを、「コントローラに対する機能テスト(functional test)を作成する」と呼んでいます。コントローラはアプリケーションが受け付けたWebリクエストを処理し、レンダリングされたビューの形で最終的なレスポンスを返すことを思い出しましょう。機能テストでは、アクションがリクエストや期待される結果(レスポンス、場合によってはHTMLビュー)をどう扱っているかをテストします。
機能テストでは以下のようなテスト項目を実施する必要があります。
実際の機能テストを最も簡単に見る方法は、scaffoldジェネレータでコントローラを生成することです。
$ bin/rails generate scaffold_controller article title:string body:text ... create app/controllers/articles_controller.rb ... invoke test_unit create test/controllers/articles_controller_test.rb ...
Article
リソースのコントローラコードとテストが生成され、test/controllers
にあるarticles_controller_test.rb
ファイルを開けるようになります。
既にコントローラがあり、7つのデフォルトのアクションごとにテスト用のscaffoldコードだけを生成したい場合は、以下のコマンドを実行します。
$ bin/rails generate test_unit:scaffold article ... invoke test_unit create test/controllers/articles_controller_test.rb ...
articles_controller_test.rb
ファイルのtest_should_get_index
というテストについて見てみます。
# articles_controller_test.rb class ArticlesControllerTest < ActionDispatch::IntegrationTest test "should get index" do get articles_url assert_response :success end end
test_should_get_index
というテストでは、Railsがindex
という名前のアクションに対するリクエストをシミュレートします。同時に、有効なarticles
インスタンス変数がコントローラに割り当てられます。
get
メソッドはWebリクエストを開始し、結果を@response
として返します。このメソッドには以下の6つの引数を渡すことができます。
articles_url
など)。params
: アクションに渡すリクエストパラメータのハッシュ(クエリの文字列パラメータまたはarticle変数など)。headers
: リクエストで渡されるヘッダーの設定に用いる。env
: リクエストの環境を必要に応じてカスタマイズするのに用いる。xhr
: リクエストがAjaxかどうかを指定する。Ajaxの場合はtrue
を設定。as
: 別のcontent typeでエンコードされているリクエストに用いる。デフォルトで:json
をサポート。上のキーワード引数はすべてオプションです。
例: 1件目のArticle
で:show
アクションを呼び出し、HTTP_REFERER
ヘッダを設定する。
get article_url(Article.first), headers: { "HTTP_REFERER" => "http://example.com/home" }
別の例: 最後のArticle
で:update
アクションをAjaxリクエストとして呼び出し、paramsのtitle
に新しいテキストを渡す。
patch article_url(Article.last), params: { article: { title: "updated" } }, xhr: true
さらに別の例: :create
アクションを呼び出して新規記事を1件作成し、タイトルに使うテキストをJSONリクエストとしてparamsに渡す。
post articles_path, params: { article: { title: "Ahoy!" } }, as: :json
articles_controller_test.rb
ファイルにあるtest_should_create_article
テストを実行してみると、モデルレベルのバリデーションが新たに追加されることによってテストは失敗します。
articles_controller_test.rb
ファイルのtest_should_create_article
テストを変更して、テストがパスするようにしてみましょう。
test "should create article" do assert_difference("Article.count") do post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } } end assert_redirected_to article_path(Article.last) end
これで、すべてのテストを実行するとパスするようになったはずです。
BASIC認証セクションの手順に沿う場合は、すべてのテストをパスさせるためにsetup
ブロックに以下を追加する必要があります。
post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } }, headers: { Authorization: ActionController::HttpAuthentication::Basic.encode_credentials("dhh", "secret") }
デフォルトのRailsは、テスト中に発生した例外のrescue
を試み、HTMLエラーページで応答します。この振る舞いはconfig.action_dispatch.show_exceptions
設定で制御できます。
HTTPリクエストに精通していれば、get
がHTTPリクエストの一種であることも既に理解していることでしょう。Railsの機能テストでは以下の6種類のHTTPリクエストがサポートされています。
get
post
patch
put
head
delete
これらはすべてメソッドとして利用できます。典型的なCRUDアプリケーションでよく使われるのはget
、post
、put
、delete
です。
機能テストによる検証では、そのリクエストがアクションで受け付けられるかどうかよりも結果を重視します。アクションが受け付けられるかどうかについては、リクエストテストの方が適切です。
get
、post
、patch
、put
、delete
メソッドで、次のようにxhr: true
を指定することでAjaxリクエストをテストできます。
test "ajax request" do article = articles(:one) get article_url(article), xhr: true assert_equal "hello world", @response.body assert_equal "text/javascript", @response.media_type end
リクエストが完了して処理されると、以下の3つのハッシュオブジェクトが利用可能になります。
cookies
- 設定されているすべてのcookies。flash
- flash内のすべてのオブジェクト。session
- セッション変数に含まれるすべてのオブジェクト。これらのハッシュは、通常のHashオブジェクトと同様に文字列をキーとして値を参照できます。たとえば次のようにシンボル名による参照も可能です。
flash["gordon"] # flash[:gordon]も可 session["shmession"] # session[:shmession]も可 cookies["are_good_for_u"] # cookies[:are_good_for_u]も可
機能テストの以下の3つの専用インスタンス変数は、リクエストが完了した後で使えるようになります。
@controller
- リクエストを処理するコントローラ@request
- リクエストオブジェクト@response
- レスポンスオブジェクトclass ArticlesControllerTest < ActionDispatch::IntegrationTest test "should get index" do get articles_url assert_equal "index", @controller.action_name assert_equal "application/x-www-form-urlencoded", @request.media_type assert_match "Articles", @response.body end end
# HTTPヘッダーを設定する get articles_url, headers: { "Content-Type": "text/plain" } # カスタムヘッダーでリクエストをシミュレートする # CGI変数を設定する get articles_url, headers: { "HTTP_REFERER": "http://example.com/home" } # カスタム環境変数でリクエストをシミュレートする
flash
通知をテストするflash
は、上述の「暗黙の」3つのヘッダーの1つです。
誰かがこのブログアプリケーションで記事を1件作成するのに成功したらflash
メッセージを追加したいと思います。
このアサーションをtest_should_create_article
テストに追加してみましょう。
test "should create article" do assert_difference("Article.count") do post articles_url, params: { article: { title: "Some title" } } end assert_redirected_to article_path(Article.last) assert_equal "Article was successfully created.", flash[:notice] end
この時点でテストを実行すると、以下のように失敗するはずです。
$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article Run options: -n test_should_create_article --seed 32266 # Running: F Finished in 0.114870s, 8.7055 runs/s, 34.8220 assertions/s. 1) Failure: ArticlesControllerTest#test_should_create_article [/test/controllers/articles_controller_test.rb:16]: --- expected +++ actual @@ -1 +1 @@ -"Article was successfully created." +nil 1 runs, 4 assertions, 1 failures, 0 errors, 0 skips
今度はコントローラにflashメッセージを実装してみましょう。:create
アクションは次のようになります。
def create @article = Article.new(article_params) if @article.save flash[:notice] = "Article was successfully created." redirect_to @article else render "new" end end
テストを実行すると、今度はパスするはずです。
$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article Run options: -n test_should_create_article --seed 18981 # Running: . Finished in 0.081972s, 12.1993 runs/s, 48.7972 assertions/s. 1 runs, 4 assertions, 0 failures, 0 errors, 0 skips
この時点でのArticles
コントローラの:index
アクションは:new
や:create
と同様にテストされるようになりました。既存のデータの扱いはどう書くのでしょうか?
:show
アクションのテストを書いてみましょう。
test "should show article" do article = articles(:one) get article_url(article) assert_response :success end
先ほどフィクスチャについて解説したように、articles()
メソッドはArticlesフィクスチャにアクセスできることを思い出しましょう。
既存の記事の削除は次のようにテストします。
test "should destroy article" do article = articles(:one) assert_difference("Article.count", -1) do delete article_url(article) end assert_redirected_to articles_path end
既存の記事の更新も次のように書けます。
test "should update article" do article = articles(:one) patch article_url(article), params: { article: { title: "updated" } } assert_redirected_to article_path(article) # 更新されたデータをフェッチするために関連付けをリロードし、タイトルが更新されたというアサーションを行う article.reload assert_equal "updated", article.title end
3つのテストは、どれも同じArticleフィクスチャデータへのアクセスを行っており、少々重複していますので、DRYにしましょう。これはActiveSupport::Callbacks
が提供するsetup
メソッドとteardown
メソッドで行います。
ここまでで、テストは次のようになっているはずです。残りのテストはとりあえずこのままにしておくことにします。
require "test_helper" class ArticlesControllerTest < ActionDispatch::IntegrationTest # 各テストの実行前に呼ばれる setup do @article = articles(:one) end # 各テストの実行後に呼ばれる teardown do # コントローラがキャッシュを使っている場合、テスト後にリセットしておくとよい Rails.cache.clear end test "should show article" do # セットアップ時の@articleインスタンス変数を再利用 get article_url(@article) assert_response :success end test "should destroy article" do assert_difference("Article.count", -1) do delete article_url(@article) end assert_redirected_to articles_path end test "should update article" do patch article_url(@article), params: { article: { title: "updated" } } assert_redirected_to article_path(@article) # 更新されたデータをフェッチするために関連付けをリロードし、タイトルが更新されたというアサーションを行う @article.reload assert_equal "updated", @article.title end end
Railsの他のコールバックと同様、setup
メソッドとteardown
メソッドにブロックやlambdaを渡したりメソッド名のシンボルで呼び出すこともできます。
コードの重複を回避するために独自のテストヘルパーを追加できます。サインインヘルパーはその良い例です。
# test/test_helper.rb module SignInHelper def sign_in_as(user) post sign_in_url(email: user.email, password: user.password) end end class ActionDispatch::IntegrationTest include SignInHelper end
require "test_helper" class ProfileControllerTest < ActionDispatch::IntegrationTest test "should show profile" do # ヘルパーがどのコントローラテストケースでも再利用可能になっている sign_in_as users(:david) get profile_url assert_response :success end end
ヘルパーが増えてtest_helper.rb
が散らかってきたことに気づいたら、別のファイルに切り出せます。切り出したファイルの置き場所はtest/lib
やtest/test_helpers
がよいでしょう。
# test/test_helpers/multiple_assertions.rb module MultipleAssertions def assert_multiple_of_forty_two(number) assert (number % 42 == 0), "expected #{number} to be a multiple of 42" end end
これらのヘルパーは、必要に応じて明示的にrequire
およびinclude
できます。
require "test_helper" require "test_helpers/multiple_assertions" class NumberTest < ActiveSupport::TestCase include MultipleAssertions test "420 is a multiple of forty two" do assert_multiple_of_forty_two 420 end end
または、関連する親クラス内で直接include
することもできます。
# test/test_helper.rb require "test_helpers/sign_in_helper" class ActionDispatch::IntegrationTest include SignInHelper end
ヘルパーをtest_helper.rb
内でeagerにrequire
できると、テストファイルが暗黙でヘルパーにアクセスできるので便利です。これは以下のようなglob記法(*
)で実現できます。
# test/test_helper.rb Dir[Rails.root.join("test", "test_helpers", "**", "*.rb")].each { |file| require file }
この方法のデメリットは、個別のテストで必要なファイルだけをrequire
するよりも起動時間が長くなることです。
Railsアプリケーションの他のあらゆる部分と同様、ルーティングもテストできます。ルーティングのテストはtest/controllers/
に配置するか、コントローラテストの一部として書きます。
アプリケーションのルーティングが複雑な場合は、Railsが提供する多くの便利なルーティングヘルパーを使えます。
Railsで使えるルーティングアサーションについて詳しくは、ActionDispatch::Assertions::RoutingAssertions
のAPIドキュメントを参照してください。
アプリケーションのビューのテストでは、リクエストに対するレスポンスをテストするために、あるページで重要なHTML要素とその内容がレスポンスに含まれているというアサーションを書くことがよくあります。ルーティングのテストと同様に、ビューテストもtest/controllers/
に配置するか、コントローラテストの一部に含めます。assert_select
というアサーションメソッドを使うと、簡潔かつ強力な文法でレスポンスのHTML要素を調べられるようになります。
assert_select
には2つの書式があります。
assert_select(セレクタ, [条件], [メッセージ])
という書式は、セレクタで指定された要素が条件に一致することを主張します。セレクタにはCSSセレクタの式(文字列)や代入値を持つ式を使えます。
assert_select(要素, セレクタ, [条件], [メッセージ])
は、選択されたすべての要素が条件に一致することを主張します。選択される要素は、element(Nokogiri::XML::Node
やNokogiri::XML::NodeSet
のインスタンス)からその子孫要素までの範囲から選択されます。
たとえば、レスポンスに含まれるtitle要素の内容を検証するには、以下のアサーションを使います。
assert_select "title", "Welcome to Rails Testing Guide"
より詳しくテストするために、ネストしたassert_select
ブロックを用いることもできます。
以下の例の場合、外側のassert_select
で選択されたすべての要素の完全なコレクションに対して、内側のassert_select
がアサーションを実行します。
assert_select "ul.navigation" do assert_select "li.menu_item" end
選択された要素のコレクションをイテレート(列挙)し、assert_select
が要素ごとに呼び出されるようにすることもできます。
たとえば、レスポンスに2つの順序付きリストがあり、1つの順序付きリストにつき要素が4つあれば、以下のテストはどちらもパスします。
assert_select "ol" do |elements| elements.each do |element| assert_select element, "li", 4 end end assert_select "ol" do assert_select "li", 8 end
assert_select
はきわめて強力なアサーションです。このアサーションの高度な利用法についてはAPIドキュメントを参照してください。
主にビューをテストするためのアサーションは他にもあります。
アサーション | 目的 |
---|---|
assert_select_email |
メールの本文に対するアサーションを行なう。 |
assert_select_encoded |
エンコードされたHTMLに対するアサーションを行なう。各要素の内容はデコードされた後にそれらをブロックとして呼び出す。 |
css_select(selector) またはcss_select(element, selector)
|
selectorで選択されたすべての要素を1つの配列にしたものを返す。2番目の書式については、最初にelementがベース要素としてマッチし、続いてそのすべての子孫に対してselectorのマッチを試みる。どちらの場合も、何も一致しなかった場合には空の配列を1つ返す。 |
assert_select_email
の利用例を以下に示します。
assert_select_email do assert_select "small", "Please click the 'Unsubscribe' link if you want to opt-out." end
パーシャル(partial template: 部分テンプレートとも)は、レンダリングプロセスを分割して管理しやすくする別の方法です。パーシャルを利用することで、コードの一部をテンプレートから別のファイルに抽出して再利用できるようになります。
ビューのテストは、パーシャルが期待通りにコンテンツをレンダリングするかどうかをテストする機会を提供します。ビューのパーシャルのテストはtest/views/
に配置し、ActionView::TestCase
を継承します。
パーシャルをレンダリングするには、テンプレート内で行うのと同様にrender
を呼び出します。レンダリングしたコンテンツは、test環境やローカル環境の#rendered
メソッドを通じて利用できるようになります。
class ArticlePartialTest < ActionView::TestCase test "renders a link to itself" do article = Article.create! title: "Hello, world" render "articles/article", article: article assert_includes rendered, article.title end end
ActionView::TestCase
を継承するテストでは、rails-dom-testing gemが提供するassert_select
などのビューベースの追加アサーションも利用できるようになります。
test "renders a link to itself" do article = Article.create! title: "Hello, world" render "articles/article", article: article assert_select "a[href=?]", article_url(article), text: article.title end
ActionView::TestCase
から継承したテストでは、document_root_element
メソッドを宣言することでrails-dom-testing gemと統合されます。このメソッドは、レンダリングされたコンテンツをNokogiri::XML::Node
のインスタンスとして返します。
test "renders a link to itself" do article = Article.create! title: "Hello, world" render "articles/article", article: article anchor = document_root_element.at("a") assert_equal article.name, anchor.text assert_equal article_url(article), anchor["href"] end
アプリケーションでRuby3.0以上を利用している場合は、RubyのパターンマッチングをサポートするNokogiri(1.14.0以上)とMinitest(5.18.0以上)に依存するようになります。
test "renders a link to itself" do article = Article.create! title: "Hello, world" render "articles/article", article: article anchor = document_root_element.at("a") url = article_url(article) assert_pattern do anchor => { content: "Hello, world", attributes: [{ name: "href", value: url }] } end end
機能テストやシステムテストで使われているのと同じCapybaraベースのアサーションにアクセスしたい場合は、ActionView::TestCase
を継承するベースクラスを以下のように定義することでdocument_root_element
をpage
メソッドに変換できます。
# test/view_partial_test_case.rb require "test_helper" require "capybara/minitest" class ViewPartialTestCase < ActionView::TestCase include Capybara::Minitest::Assertions def page Capybara.string(rendered) end end
# test/views/article_partial_test.rb require "view_partial_test_case" class ArticlePartialTest < ViewPartialTestCase test "renders a link to itself" do article = Article.create! title: "Hello, world" render "articles/article", article: article assert_link article.title, href: article_url(article) end end
Action View 7.1以降の#rendered
ヘルパーメソッドは、ビューパーシャルでレンダリングされたコンテンツを解析できるオブジェクトを返すようになります。
#rendered
メソッドが返すString
コンテンツをオブジェクトに変換するには、.register_parser
を呼び出してパーサーを定義します。.register_parser :rss
を呼び出せば、#rendered.rss
ヘルパーメソッドが定義されます。
たとえば、レンダリングしたRSSコンテンツを#rendered.rss
で解析してオブジェクトにする場合は、以下のようにRSS::Parser.parse
呼び出しを登録します。
register_parser :rss, -> rendered { RSS::Parser.parse(rendered) } test "renders RSS" do article = Article.create!(title: "Hello, world") render formats: :rss, partial: article assert_equal "Hello, world", rendered.rss.items.last.title end
ActionView::TestCase
には、デフォルトで以下のパーサーが定義されています。
:html
: Nokogiri::XML::Node
のインスタンスを返します:json
: ActiveSupport::HashWithIndifferentAccess
のインスタンスを返しますtest "renders HTML" do article = Article.create!(title: "Hello, world") render partial: "articles/article", locals: { article: article } assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } } end
test "renders JSON" do article = Article.create!(title: "Hello, world") render formats: :json, partial: "articles/article", locals: { article: article } assert_pattern { rendered.json => { title: "Hello, world" } } end
ヘルパー自体は単なるシンプルなモジュールであり、ビューから利用するヘルパーメソッドをこの中に定義します。
ヘルパーのテストについては、ヘルパーメソッドの出力が期待どおりであるかどうかをチェックするだけで十分です。ヘルパー関連のテストはtest/helpers
ディレクトリに置かれます。
以下のようなヘルパーがあるとします。
module UsersHelper def link_to_user(user) link_to "#{user.first_name} #{user.last_name}", user end end
このメソッドの出力は次のようにしてテストできます。
class UsersHelperTest < ActionView::TestCase test "should return the user's full name" do user = users(:david) assert_dom_equal %{<a href="/user/#{user.id}">David Heinemeier Hansson</a>}, link_to_user(user) end end
さらに、テストクラスはActionView::TestCase
を継承しているので、link_to
やpluralize
などのRailsヘルパーメソッドにアクセスできます。
メーラークラスを十分にテストするためには特殊なツールが若干必要になります。
Railsアプリケーションの他の部分と同様、メーラークラスについても期待どおり動作するかどうかをテストする必要があります。
メーラークラスをテストする目的は以下を確認することです。
メーラーのテストには単体テストと機能テストの2つの側面があります。単体テストでは、完全に制御された入力を与えた結果の出力と、期待される既知の値(フィクスチャ)を比較します。機能テストではメーラーによって作成される詳細部分についてのテストはほとんど行わず、コントローラとモデルがメーラーを正しく利用しているかどうかをテストするのが普通です。メーラーのテストは、最終的に適切なメールが適切なタイミングで送信されたことを立証するために行います。
メーラーが期待どおりに動作しているかどうかをテストするために、事前に作成しておいた出力例と、メーラーの実際の出力を比較する単体テストを実行できます。
メーラーの単体テストを行なうために、フィクスチャを利用してメーラーが最終的に出力すべき外見の例を与えます。これらのフィクスチャはメールの出力例であって、通常のフィクスチャのようなActive Recordデータではないので、通常のフィクスチャとは別の専用のサブディレクトリに保存します。test/fixtures
ディレクトリの下のディレクトリ名は、メーラーの名前に対応させてください。たとえばUserMailer
という名前のメーラーであれば、test/fixtures/user_mailer
というスネークケースのディレクトリ名にします。
メーラーを生成しても、メーラーのアクションに対応するスタブフィクスチャは生成されません。これらのファイルは上述の方法で手動作成する必要があります。
invite
というアクションで知人に招待状を送信するUserMailer
という名前のメーラーに対する単体テストを以下に示します。これは、invite
アクションをジェネレータで生成したときに作成される基本的なテストに手を加えたものです。
require "test_helper" class UserMailerTest < ActionMailer::TestCase test "invite" do # さらにアサーションを行うためにメールを作成して保存 email = UserMailer.create_invite("[email protected]", "[email protected]", Time.now) # メールを送信後キューに追加されるかどうかをテスト assert_emails 1 do email.deliver_now end # 送信されたメールの本文が期待どおりの内容であるかどうかをテスト assert_equal ["[email protected]"], email.from assert_equal ["[email protected]"], email.to assert_equal "You have been invited by [email protected]", email.subject assert_equal read_fixture("invite").join, email.body.to_s end end
このテストでは、メールを送信し、その結果返されたオブジェクトをemail
変数に保存します。続いて、このメールが送信されたことを主張します(最初のアサーション)。次のアサーションでは、メールの内容が期待どおりであることを主張します。このファイルの内容をread_fixture
ヘルパーで読み出しています。
email.body.to_s
は、HTMLまたはテキストで1回出現した場合にのみ存在するとみなされます。メーラーがどちらも提供している場合は、email.text_part.body.to_s
やemail.html_part.body.to_s
を用いてそれぞれの一部に対するフィクスチャをテストできます。
invite
フィクスチャは以下のような内容にしておきます。
[email protected]さん、こんにちは。 招待状を送付いたします。 どうぞよろしく!
ここでメーラーのテスト作成方法の詳細部分について解説します。config/environments/test.rb
のActionMailer::Base.delivery_method = :test
という行で送信モードをtestに設定しています。これにより、送信したメールが実際に配信されないようにできます。そうしないと、テスト中にユーザーにスパムメールを送りつけてしまうことになります。この設定で送信したメールは、ActionMailer::Base.deliveries
という配列に追加されます。
このActionMailer::Base.deliveries
という配列は、ActionMailer::TestCase
とActionDispatch::IntegrationTest
でのテストを除き、自動的にはリセットされません。それらのテストの外で配列をクリアしたい場合は、ActionMailer::Base.deliveries.clear
で手動リセットできます。
Assert_enqueued_email_with
アサーションを使えば、期待されるメーラーメソッドの引数やパラメータをすべて利用してメールがエンキュー(enqueue)されたことを確認できます。これにより、deliver_later
メソッドでエンキューされたすべてのメールにマッチできるようになります。
基本的なテストケースと同様に、メールを作成し、返されたオブジェクトをemail
変数に保存します。引数やパラメータを渡すテスト例をいくつか紹介します。
以下の例は、メールが正しい引数でエンキューされたことを主張します。
require "test_helper" class UserMailerTest < ActionMailer::TestCase test "invite" do # メールを作成し、今後さらにアサーションするために保存する email = UserMailer.create_invite("[email protected]", "[email protected]") # 正しい引数でメールがエンキューされたことをテストする assert_enqueued_email_with UserMailer, :create_invite, args: ["[email protected]", "[email protected]"] do email.deliver_later end end end
以下の例は、引数のハッシュをargs
として渡すことで、メーラーメソッドの正しい名前付き引数でメーラーがエンキューされたことを主張します。
require "test_helper" class UserMailerTest < ActionMailer::TestCase test "invite" do # メールを作成し、今後さらにアサーションするために保存する email = UserMailer.create_invite(from: "[email protected]", to: "[email protected]") # 正しい名前付き引数でメールがエンキューされたことをテストする assert_enqueued_email_with UserMailer, :create_invite, args: [{ from: "[email protected]", to: "[email protected]" }] do email.deliver_later end end end
以下の例は、パラメータ化されたメーラーが正しいパラメータと引数でエンキューされたことを主張します。メーラーのパラメータはparams
として、メーラーメソッドの引数はargs
として渡されます:
require "test_helper" class UserMailerTest < ActionMailer::TestCase test "invite" do # メールを作成し、今後さらにアサーションするために保存する email = UserMailer.with(all: "good").create_invite("[email protected]", "[email protected]") # パラメータと引数の正しいメーラーでメールがエンキューされたことをテストする assert_enqueued_email_with UserMailer, :create_invite, params: { all: "good" }, args: ["[email protected]", "[email protected]"] do email.deliver_later end end end
以下の例は、パラメータ化されたメーラーが正しいパラメータでエンキューされたかどうかをテストする別の方法です。
require "test_helper" class UserMailerTest < ActionMailer::TestCase test "invite" do # メールを作成し、今後さらにアサーションするために保存する email = UserMailer.with(to: "[email protected]").create_invite # パラメータの正しいメーラーでメールがエンキューされたことをテストする assert_enqueued_email_with UserMailer.with(to: "[email protected]"), :create_invite do email.deliver_later end end end
単体テストはメールの属性をテストできますが、機能テストとシステムテストを使えば、配信するメールがユーザー操作によって適切にトリガーされているかどうかをテストできます。たとえば、友人を招待する操作によってメールが適切に送信されたかどうかを以下のようにチェックできます。
# 結合テスト require "test_helper" class UsersControllerTest < ActionDispatch::IntegrationTest test "invite friend" do # Asserts the difference in the ActionMailer::Base.deliveries assert_emails 1 do post invite_friend_url, params: { email: "[email protected]" } end end end
# システムテスト require "test_helper" class UsersTest < ActionDispatch::SystemTestCase driven_by :selenium, using: :headless_chrome test "inviting a friend" do visit invite_users_url fill_in "Email", with: "[email protected]" assert_emails 1 do click_on "Invite" end end end
assert_emails
メソッドは特定の配信方法に紐付けられておらず、deliver_now
メソッドとdeliver_later
メソッドのどちらでメールを配信する場合にも利用できます。メールがキューに登録されたことを明示的なアサーションにしたい場合は、assert_enqueued_email_with
メソッド(上述の例を参照)か、assert_enqueued_emails
メソッドを利用できます。詳しくはActionMailer::TestHelper
APIドキュメントを参照してください。
複数のジョブを分離してテストする(ジョブの振る舞いに注目する)ことも、コンテキストでテストする(呼び出し元のコードの振る舞いに注目する)ことも可能です。
ジョブを生成すると、ジョブに関連するテストもtest/jobs
ディレクトリの下に生成されます。
以下は請求(billing)ジョブの例です。
require "test_helper" class BillingJobTest < ActiveJob::TestCase test "account is charged" do perform_enqueued_jobs do BillingJob.perform_later(account, product) end assert account.reload.charged_for?(product) end end
テスト用のデフォルトキューアダプタは、perform_enqueued_jobs
が呼び出されるまでジョブを実行しません。さらに、テスト同士が干渉しないようにするため、個別のテストを実行する前にすべてのジョブをクリアします。
このテストでは、perform_enqueued_jobs
とperform_later
を使っています。perform_now
の代わりにこれらを使うことで、リトライが設定されている場合には失敗したリトライが(再度エンキューされて無視されずに)テストでキャッチされるようになります。
コントローラなどで、呼び出しのたびにジョブが正しくエンキューされているかどうかをテストするのはよい方法です。ActiveJob::TestHelper
モジュールは、そのために役立つassert_enqueued_with
などのメソッドを提供しています。
以下はAccountモデルのメソッドをテストする例です。
require "test_helper" class AccountTest < ActiveSupport::TestCase include ActiveJob::TestHelper test "#charge_for enqueues billing job" do assert_enqueued_with(job: BillingJob) do account.charge_for(product) end assert_not account.reload.charged_for?(product) perform_enqueued_jobs assert account.reload.charged_for?(product) end end
特にリトライが設定されている場合は、特定のケースでジョブが例外を発生することをテストするのが難しくなることもあります。perform_enqueued_jobs
ヘルパーは、ジョブが例外を発生するとテストが失敗するので、例外の発生時にテストを成功させるには、以下のようにジョブのperform
メソッドを直接呼び出す必要があります。
require "test_helper" class BillingJobTest < ActiveJob::TestCase test "does not charge accounts with insufficient funds" do assert_raises(InsufficientFundsError) do BillingJob.new(empty_account, product).perform end assert_not account.reload.charged_for?(product) end end
この方法はフレームワークの一部(引数のシリアライズなど)を回避するため、一般には推奨されていません。
Action Cableはアプリケーション内部の異なるレベルで用いられるため、チャネル、コネクションのクラス自身、および他のエンティティがいずれも正しいメッセージをブロードキャストすることをテストする必要があります。
デフォルトでは、Action Cableを用いる新しいRailsアプリを生成すると、基本のコネクションクラス(ApplicationCable::Connection
)のテストもtest/channels/application_cable
ディレクトリの下に生成されます。
コネクションテストの目的は、あるコネクションのidが正しく代入されているか、正しくないコネクションリクエストを却下できるかどうかをチェックすることです。以下はテスト例です。
class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase test "connects with params" do # `connect`メソッドを呼ぶことでコネクションのオープンをシミュレートする connect params: { user_id: 42 } # テストでは`connection`でConnectionオブジェクトにアクセスできる assert_equal connection.user_id, "42" end test "rejects connection without params" do # コネクションが却下されたことを # `assert_reject_connection`マッチャーで検証する assert_reject_connection { connect } end end
リクエストのcookieも、結合テストの場合と同様の方法で指定できます。
test "connects with cookies" do cookies.signed[:user_id] = "42" connect assert_equal connection.user_id, "42" end
詳しくはActionCable::Connection::TestCase
APIドキュメントを参照してください。
デフォルトでは、チャネルを1つ生成するときにtest/channels
ディレクトリの下に関連するテストも生成されます。以下はチャットチャネルでのテスト例です。
require "test_helper" class ChatChannelTest < ActionCable::Channel::TestCase test "subscribes and stream for room" do # `subscribe`を呼ぶことでサブスクリプション作成をシミュレートする subscribe room: "15" # テストでは`subscription`でChannelオブジェクトにアクセスできる assert subscription.confirmed? assert_has_stream "chat_15" end end
このテストはかなりシンプルであり、チャネルが特定のストリームへのコネクションをサブスクライブするアサーションしかありません。
背後のコネクションidも指定できます。以下はWeb通知チャネルのテスト例です。
require "test_helper" class WebNotificationsChannelTest < ActionCable::Channel::TestCase test "subscribes and stream for user" do stub_connection current_user: users(:john) subscribe assert_has_stream_for users(:john) end end
詳しくはActionCable::Channel::TestCase
APIドキュメントを参照してください。
Action Cableには、冗長なテストを削減するのに使えるカスタムアサーションが多数用意されています。利用できる全アサーションのリストについては、ActionCable::TestHelper
APIドキュメントを参照してください。
正しいメッセージがブロードキャストされたことを(コントローラ内部などの)他のコンポーネント内で確認するのはよい方法です。Action Cableが提供するカスタムアサーションの有用さは、まさにここで発揮されます。たとえばモデル内では以下のように書けます。
require "test_helper" class ProductTest < ActionCable::TestCase test "broadcast status after charge" do assert_broadcast_on("products:#{product.id}", type: "charged") do product.charge(account) end end end
Channel.broadcast_to
によるブロードキャストをテストしたい場合は、Channel.broadcasting_for
で背後のストリーム名を生成します。
# app/jobs/chat_relay_job.rb class ChatRelayJob < ApplicationJob def perform(room, message) ChatChannel.broadcast_to room, text: message end end
# test/jobs/chat_relay_job_test.rb require "test_helper" class ChatRelayJobTest < ActiveJob::TestCase include ActionCable::TestHelper test "broadcast message to room" do room = rooms(:all) assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Hi!") do ChatRelayJob.perform_now(room, "Hi!") end end end
通常、アプリケーションはdevelopment
環境やtest
環境で高速化のためにeager loadingを行うことはありませんが、production`環境では行います。
プロジェクトのファイルの一部が何らかの理由で読み込めない場合、production
環境にデプロイする前にそのことを検出する方がよいでしょう。
プロジェクトでCI(Continuous Integration: 継続的インテグレーション)を利用している場合、アプリケーションでeager loadingを確実に行う手軽な方法の1つは、CIでeager loadingすることです。
CIは、テストスイートがそこで実行されていることを示すために、以下のように何らかの環境変数(CI
など)を設定するのが普通です。
# config/environments/test.rb config.eager_load = ENV["CI"].present?
Rails 7からは、新規生成されたアプリケーションでこの設定がデフォルトで行われます。
プロジェクトでCIを利用していない場合は、以下のようにRails.application.eager_load!
を呼び出すことでテストスイートをeager loadingできるようになります。
require "test_helper" class ZeitwerkComplianceTest < ActiveSupport::TestCase test "eager loads all files without errors" do assert_nothing_raised { Rails.application.eager_load! } end end
require "rails_helper" RSpec.describe "Zeitwerk compliance" do it "eager loads all files without errors" do expect { Rails.application.eager_load! }.not_to raise_error end end
Railsには、時間の影響を受けやすいコードが期待どおりに動作しているというアサーションに役立つ組み込みのヘルパーメソッドを提供しています。
以下の例ではtravel_to
ヘルパーを使っています。
# 登録後のユーザーは1か月分の特典が有効だとする user = User.create(name: "Gaurish", activation_date: Date.new(2004, 10, 24)) assert_not user.applicable_for_gifting? travel_to Date.new(2004, 11, 24) do assert_equal Date.new(2004, 10, 24), user.activation_date #`travel_to`ブロック内では`Date.current`がモック化される assert user.applicable_for_gifting? end assert_equal Date.new(2004, 10, 24), user.activation_date # この変更は`travel_to`ブロック内からしか見えない
時間関連のヘルパーについて詳しくは、ActiveSupport::Testing::TimeHelpers
APIドキュメントを参照してください。
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。