何について書いているか
Railsチュートリアル第11章、演習 - 有効化のテストとリファクタリングのうち、3番目の演習内容について書いています。
ここまでの演習課題で変更したコードをテストするために、/users と /users/:id の両方に対する統合テストを作成してみましょう。
統合テストの作成
テストの名前はusers_activation
とします。
# rails generate integration_test users_activation
Running via Spring preloader in process 14164
invoke test_unit
create test/integration/users_activation_test.rb
当該統合テストの前提となる実装を行う
fixtureに、有効でないユーザーを定義する
現状のfixtureには、有効でないユーザーは定義されていません。テストを実行する前に、まずは有効でないユーザーがRDB上に存在するよう、fixtureにユーザーを追加する必要があります。
fixtureで有効でないユーザーを生成する場合、例えば以下のようなYAMLが必要となります。
not_activated:
name: User Not activated
email: example_foobar@example.org
password_digest: <%= User.digest('password') %>
activated: false
activated_at: nil
ポイントは以下です。
-
activated
がfalse
であること -
activated_at
がnil
であること
UsersActivationTest#setup
メソッド
実際のテストに先がけて、テストで使うユーザー情報をインスタンス変数に格納する必要があります。そうした処理は、テストのsetup
メソッドで行うのでしたね。
def setup
@admin = users(:rhakurei)
@user_not_activated = users(:not_activated)
end
以下の処理を行っています。
- 管理ユーザーを
@admin
として定義する- /users を表示するために、有効なユーザーが必要となる
- 有効でないユーザーを
@user_not_activated
として定義する
必要なテストの内容
必要なテストの内容は以下になります。
- /users で表示されるユーザー一覧に、有効でないユーザーが表示されないこと
- /users/:id で有効でないユーザーを表示しようとしたときに、/ にリダイレクトされること
/users で表示されるユーザー一覧に、有効でないユーザーが表示されないこと
名前は「index should not include not activated user」としました
test "index should not include not activated user" do
log_in_as(@admin)
get users_path
assert_select 'a[href=?]', user_path(@user_not_activated), count: 0
end
動作内容は以下となります。
- 管理ユーザーでログインする
-
users_path
に対し、GET
リクエストを送信する - 有効でないユーザーのプロフィールページへのリンクが表示されていなければテスト成功
/users/:id で有効でないユーザーを表示しようとしたときに、/ にリダイレクトされること
名前は「show user who is not activated should redirect to root」としました。
test "show user who is not activated should redirect to root" do
get user_path(@user_not_activated)
assert_redirected_to root_url
end
動作内容は以下となります。
- 有効でないユーザーの
user_path
に対し、GET
リクエストを送信する - / にリダイレクトされればテスト成功
test/integration/users_activation_test.rb
の全体像
test/integration/users_activation_test.rb
の全体像は以下のようになります。
require 'test_helper'
class UsersActivationTest < ActionDispatch::IntegrationTest
def setup
@admin = users(:rhakurei)
@user_not_activated = users(:not_activated)
end
test "index should not include not activated user" do
log_in_as(@admin)
get users_path
assert_select 'a[href=?]', user_path(@user_not_activated), count: 0
end
test "show user who is not activated should redirect to root" do
get user_path(@user_not_activated)
assert_redirected_to root_url
end
end
この時点でテストは成功する
この時点で、test/integration/users_activation_test.rb
を対象にテストを実行すると、問題なく成功します。
# rails test test/integration/users_activation_test.rb
Running via Spring preloader in process 14245
Started with run options --seed 11346
2/2: [===================================] 100% Time: 00:00:03, Time: 00:00:03
Finished in 3.75769s
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
逆にどのようなバグがあったらテストが失敗するのか
/users で表示されるユーザー一覧に、有効でないユーザーが表示される場合
仮に、UsersController#index
の実装が以下のようになっていたら、テストの結果はどうなるでしょう。
def index
@users = User.paginate(page: params[:page])
end
以下のようなメッセージが表示されてテストが失敗します。
# rails test test/integration/users_activation_test.rb
Running via Spring preloader in process 14284
Started with run options --seed 11740
/0: [=---=---=---=---=---=---=---=---=---=-] 0% Time: 00:00:00, ETA: ??:??:??
[8, 17] in /var/www/sample_app/test/integration/users_activation_test.rb
8:
9: test "index should not include not activated user" do
10: log_in_as(@admin)
11: get users_path
12: debugger
=> 13: assert_select 'a[href=?]', user_path(@user_not_activated), count: 0
14: end
15: ...略
(byebug) user_path(@user_not_activated)
"/users/826701971"
FAIL["test_index_should_not_include_not_activated_user", UsersActivationTest, 11.721780299965758]
test_index_should_not_include_not_activated_user#UsersActivationTest (11.72s)
Expected exactly 0 elements matching "a[href="/users/826701971"]", found 2..
Expected: 0
Actual: 2
test/integration/users_activation_test.rb:13:in `block in <class:UsersActivationTest>'
2/2: [===================================] 100% Time: 00:00:11, Time: 00:00:11
Finished in 11.76151s
2 tests, 2 assertions, 1 failures, 0 errors, 0 skips
/users/826701971 というのは、user_path(@user_not_activated)
のことです(わかりやすくするために、ここではassert_select
の前にdebugger
で確認しています)。
なお、a[href="/users/826701971"]
の数の「2」というのは、GET
用のリンクとDELETE
用のリンクで2つ、ということです。ログインユーザーが管理ユーザーでない場合、ここは「1」となるはずですね。
/users に有効でないユーザーが表示される - どう修正すればいいか
/index で有効なユーザーのみを表示するようにするためには、@users
を定義するメソッドチェーンにwhere(activated: true)
が必要なのでしたね。
def index
- @users = User.paginate(page: params[:page])
+ @users = User.where(activated: true).paginate(page: params[:page])
end
今度こそテストは成功するはずです。
# rails test test/integration/users_activation_test.rb
Running via Spring preloader in process 14323
Started with run options --seed 3334
2/2: [===================================] 100% Time: 00:00:03, Time: 00:00:03
Finished in 3.32350s
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
/users/:id で有効でないユーザーを表示しようとしたときに、/ にリダイレクトされない場合
仮に、UsersController#show
の実装が以下のようになっていたら、テストの結果はどうなるでしょう。
def show
@user = User.find(params[:id])
end
以下のようなメッセージが表示されてテストが失敗します。
# rails test test/integration/users_activation_test.rb
Running via Spring preloader in process 14310
Started with run options --seed 41248
FAIL["test_show_user_who_is_not_activated_should_redirect_to_root", UsersActivationTest, 2.460988399980124]
test_show_user_who_is_not_activated_should_redirect_to_root#UsersActivationTest (2.46s)
Expected response to be a <3XX: redirect>, but was a <200: OK>
test/integration/users_activation_test.rb:17:in `block in <class:UsersActivationTest>'
2/2: [===================================] 100% Time: 00:00:03, Time: 00:00:03
Finished in 3.07865s
2 tests, 2 assertions, 1 failures, 0 errors, 0 skips
「リダイレクトされるべき場面でリダイレクトされていない」という趣旨のメッセージですね。
有効でないユーザーを表示しようとしたときに、/ にリダイレクトされない - どう修正すればいいか
UsersController#show
を以下のように修正します。
def show
@user = User.find(params[:id])
+ redirect_to root_url unless @user.activated?
end
今度こそテストは成功するはずです。
# rails test test/integration/users_activation_test.rb
Running via Spring preloader in process 14336
Started with run options --seed 35088
2/2: [===================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.52763s
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
test/integration/users_index_test.rb
にも修正が必要だった
テスト失敗の内容
fixtureに有効でないユーザーを含むようにすると、全体のテストが成功しなくなります。
# rails test
Running via Spring preloader in process 14362
Started with run options --seed 17156
FAIL["test_index_as_admin_including_pagination_and_delete_links", UsersIndexTest, 6.323125800001435]
test_index_as_admin_including_pagination_and_delete_links#UsersIndexTest (6.32s)
Expected at least 1 element matching "a[href="/users/826701971"]", found 0..
Expected 0 to be >= 1.
test/integration/users_index_test.rb:16:in `block (2 levels) in <class:UsersIndexTest>'
test/integration/users_index_test.rb:15:in `block in <class:UsersIndexTest>'
46/46: [=================================] 100% Time: 00:00:07, Time: 00:00:07
Finished in 7.62021s
46 tests, 188 assertions, 1 failures, 0 errors, 0 skips
対応するテスト
test_index_as_admin_including_pagination_and_delete_links#UsersIndexTest
に対応するソースコードは、test/integration/users_index_test.rb
の以下の部分ですね。
test "index as admin including pagination and delete links" do
log_in_as(@admin)
get users_path
assert_template 'users/index'
assert_select 'div.pagination'
first_page_of_users = User.paginate(page: 1)
first_page_of_users.each do |user|
assert_select 'a[href=?]', user_path(user), text: user.name
unless user == @admin
assert_select 'a[href=?]', user_path(user), text: 'delete'
end
end
assert_difference 'User.count', -1 do
delete user_path(@non_admin)
end
end
原因と修正
原因は以下の行です。1ページ目に表示されるユーザーの組を、「有効でないユーザーを含む」という形で定義してしまっているためですね。
first_page_of_users = User.paginate(page: 1)
当該実装は、以下のように修正する必要があります。「1ページ目に表示されるユーザーの組に、有効なユーザーのみを含むようにする」という実装ですね。
- first_page_of_users = User.paginate(page: 1)
+ first_page_of_users = User.where(activated: true).paginate(page: 1)
全体としては以下の修正内容となります。
test "index as admin including pagination and delete links" do
log_in_as(@admin)
get users_path
assert_template 'users/index'
assert_select 'div.pagination'
- first_page_of_users = User.paginate(page: 1)
+ first_page_of_users = User.where(activated: true).paginate(page: 1)
first_page_of_users.each do |user|
assert_select 'a[href=?]', user_path(user), text: user.name
unless user == @admin
assert_select 'a[href=?]', user_path(user), text: 'delete'
end
end
assert_difference 'User.count', -1 do
delete user_path(@non_admin)
end
end
今度こそ全体のテストが成功する
当該修正を適用すれば、今度こそ全体のテストが成功するようになります。
# rails test
Running via Spring preloader in process 14401
Started with run options --seed 21435
46/46: [=================================] 100% Time: 00:00:07, Time: 00:00:07
Finished in 7.06875s
46 tests, 197 assertions, 0 failures, 0 errors, 0 skips