ユーザー登録に成功した画面のモックアップ
ユーザー登録に失敗した場合の挙動は、ここまでの学習で実装が完了しました。
ユーザー登録に成功した画面のモックアップ - ユーザー登録に成功したら、当該リンク先のような画面を出力するように実装していきます。
登録フォームの完成
現状、有効な情報で登録操作を行うと何が起こるか
現状では、ユーザー登録フォームに有効な情報で登録操作を行っても、元の画面に戻されてしまいます。
このときのrails server
のログ情報は以下のようになります。
Started POST "/users" for 172.17.0.1 at 2019-10-22 09:28:55 +0000
...略
No template found for UsersController#create, rendering head :no_content
Completed 204 No Content in 1605ms (ActiveRecord: 50.4ms)
「No template found for UsersController#create」というのがポイントですね。実際に発生しているのは以下の事態です。
- Railsはデフォルトのアクションに対応するビューを表示しようとする
-
create
アクションに対応するビューのテンプレートは現状存在しない - 元の画面に戻されてしまう
登録内容はRDBにきちんと反映される
なお、現状においても、有効な情報で登録操作を行えば、登録内容はRDBにきちんと反映されます。
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"YZ6q9xae3E23E8J5R3JNqhrVZ5r+jL9UV+QIZy7LiF/sdbfCNkYFsMUr+8tqltfCwiHus2I/viw+wT92e7nluQ==", "user"=>{"name"=>"Foobar Foobar", "email"=>"foobar@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
(0.1ms) begin transaction
User Exists (6.7ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "foobar@example.com"], ["LIMIT", 1]]
SQL (25.7ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?) [["name", "Foobar Foobar"], ["email", "foobar@example.com"], ["created_at", "2019-10-22 09:28:55.485707"], ["updated_at", "2019-10-22 09:28:55.485707"], ["password_digest", "$2a$10$qgI/adIo1AfEBIYOxU636uh/k2kEWw9IM2F/E5kdqMAdkTgeNtRV."]]
(18.0ms) commit transaction
# rails console
Running via Spring preloader in process 505
Loading development environment (Rails 5.1.6)
>> User.count
(3.0ms) SELECT COUNT(*) FROM "users"
=> 2
>> User.find(2)
User Load (4.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
=> #<User id: 2, name: "Foobar Foobar", email: "foobar@example.com", created_at: "2019-10-22 09:28:55", updated_at: "2019-10-22 09:28:55", password_digest: "$2a$10$qgI/adIo1AfEBIYOxU636uh/k2kEWw9IM2F/E5kdqMA...">
別のページへのリダイレクトを実装する
Railsの一般的な慣習では、「ユーザー登録に成功した場合、ページを描画せずに別のページにリダイレクトする」ことになっています。今回は、「ユーザー登録に成功した場合、新しく作成されたユーザーのプロフィールページにリダイレクトする」という動作にしましょう。
変更するファイルはapp/controllers/users_controller.rb
です。
class UsersController < ApplicationController
...略
def create
@user = User.new(user_params)
if @user.save
- # TODO: 保存の成功をここに実装する
+ redirect_to @user
else
render 'new'
end
end
...略
end
redirect_to @user
というコードについて
redirect_to @user
上記のコードと下記のコードは等価の挙動となります。
redirect_to user_url(@user)
上述2つのコードを等価とみなす動作も、Railsの機能の一つです。
演習 - 登録フォームの完成
1. 有効な情報を送信し、ユーザーが実際に作成されたことを、Railsコンソールを使って確認してみましょう。
有効な情報を送信した際、rails server
のログは以下のようになります。
Started POST "/users" for 172.17.0.1 at 2019-10-22 09:46:59 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"YZ6q9xae3E23E8J5R3JNqhrVZ5r+jL9UV+QIZy7LiF/sdbfCNkYFsMUr+8tqltfCwiHus2I/viw+wT92e7nluQ==", "user"=>{"name"=>"Foo Bar Baz", "email"=>"foobar.foobar@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
(0.2ms) begin transaction
User Exists (5.8ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "foobar.foobar@example.com"], ["LIMIT", 1]]
SQL (12.4ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?) [["name", "Foo Bar Baz"], ["email", "foobar.foobar@example.com"], ["created_at", "2019-10-22 09:46:59.624267"], ["updated_at", "2019-10-22 09:46:59.624267"], ["password_digest", "$2a$10$aVxeV/ey4bi5F9/DnuXRd.M0/41r/X3Sj7rl1MdKnpubgKUni9tlu"]]
(18.4ms) commit transaction
Redirected to http://localhost:8080/users/3
Completed 302 Found in 237ms (ActiveRecord: 48.6ms)
RDBに対するINSERT
文が発行された後、確かにリダイレクト処理が行われていますね。POST
リクエストの処理結果は、最終的に「302 found」となっています。
Started GET "/users/3" for 172.17.0.1 at 2019-10-22 09:46:59 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#show as HTML
Parameters: {"id"=>"3"}
User Load (3.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
Rendering users/show.html.erb within layouts/application
Rendered users/show.html.erb within layouts/application (3.0ms)
Rendered layouts/_rails_default.erb (276.7ms)
Rendered layouts/_shim.html.erb (0.4ms)
Rendered layouts/_header.html.erb (0.8ms)
Rendered layouts/_footer.html.erb (3.6ms)
Completed 200 OK in 509ms (Views: 444.4ms | ActiveRecord: 3.4ms)
最終的には、以下のような画面が表示されます。
先ほど作成したユーザー情報のemail
属性の値が"foobar.foobar@example.com"
であることを踏まえて、rails console
で、実際にRDBにユーザー情報が保存されていることを確認してみましょう。
# rails console
>> User.find_by(email: "foobar.foobar@example.com")
User Load (11.1ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "foobar.foobar@example.com"], ["LIMIT", 1]]
=> #<User id: 3, name: "Foo Bar Baz", email: "foobar.foobar@example.com", created_at: "2019-10-22 09:46:59", updated_at: "2019-10-22 09:46:59", password_digest: "$2a$10$aVxeV/ey4bi5F9/DnuXRd.M0/41r/X3Sj7rl1MdKnpu...">
確かに、実際にRDBにユーザー情報が保存されています。
2. リスト 7.28を更新し、redirect_to user_url(@user)
とredirect_to @user
が同じ結果になることを確認してみましょう。
app/controllers/users_controller.rb
を変更します。
class UsersController < ApplicationController
...略
def create
@user = User.new(user_params)
if @user.save
- redirect_to @user
+ redirect_to user_url(@user)
else
render 'new'
end
end
...略
end
以上の変更を行った上で、有効な情報を送信すると、rails server
のログは以下のようになります。
Started POST "/users" for 172.17.0.1 at 2019-10-22 10:47:07 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"YZ6q9xae3E23E8J5R3JNqhrVZ5r+jL9UV+QIZy7LiF/sdbfCNkYFsMUr+8tqltfCwiHus2I/viw+wT92e7nluQ==", "user"=>{"name"=>"Foo Bar Foobar", "email"=>"foobar.baz@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
(0.1ms) begin transaction
User Exists (6.0ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "foobar.baz@example.com"], ["LIMIT", 1]]
SQL (20.5ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?) [["name", "Foo Bar Foobar"], ["email", "foobar.baz@example.com"], ["created_at", "2019-10-22 10:47:07.789313"], ["updated_at", "2019-10-22 10:47:07.789313"], ["password_digest", "$2a$10$frfU4tFYiTifJHRfHLQ7nO.3c1ju83yJsQbklZU7pGJa2LNdXSENG"]]
(16.6ms) commit transaction
Redirected to http://localhost:8080/users/4
Completed 302 Found in 258ms (ActiveRecord: 55.5ms)
Started GET "/users/4" for 172.17.0.1 at 2019-10-22 10:47:07 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#show as HTML
Parameters: {"id"=>"4"}
User Load (5.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 4], ["LIMIT", 1]]
Rendering users/show.html.erb within layouts/application
Rendered users/show.html.erb within layouts/application (0.9ms)
Rendered layouts/_rails_default.erb (262.1ms)
Rendered layouts/_shim.html.erb (0.3ms)
Rendered layouts/_header.html.erb (0.7ms)
Rendered layouts/_footer.html.erb (0.5ms)
Completed 200 OK in 516ms (Views: 472.0ms | ActiveRecord: 5.6ms)
flash
flashとその実装
世界中に多数存在するWebアプリケーションの多くには、「新規ユーザー登録が完了した後に表示されるページにメッセージを表示し、2度目以降のログインではそのページにメッセージを表示しない」という機能が実装されています。
Railsでそのような情報を表示するには、flashという特殊な変数を使用します。その用法はRubyのハッシュと類似しています。ここでは、Railsの一般的な慣習に従い、:success
というキーには成功時のメッセージを代入するように実装します。
flashを実装する箇所は、app/controllers/users_controller.rb
内です。
class UsersController < ApplicationController
...略
def create
@user = User.new(user_params)
if @user.save
+ flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
...略
end
flash内に存在するキーがあるかを調べ、もしあればキーに対応する値(メッセージ)を全て表示する
今回はこの項見出しに示した機能を実装していきます。そういえば、第4章中のサンプルプログラムで、「ブロックを使い、特定のハッシュのkeyとvalueの組を順次表示していく」というものがありました。
>> flash = { success: "It worked!", danger: "It failed." }
>> flash.each do |key, value|
?> puts "#{key}"
>> puts "#{value}"
>> end
success
It worked!
danger
It failed.
=> {:success=>"It worked!", :danger=>"It failed."}
ここでflash
という変数名を使ったのは、今回の実装に向けての伏線だったのですね。
というわけで、flash変数の内容をWebサイト全体にわたって表示できるようにするための(仮)コードは以下のようになります。
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
(仮)とあるのは、このコードは「HTMLとERbが雑に混ざっており、リファクタリングの余地がある」ためです。Railsチュートリアル本文には、「これをキレイに整形する作業は演習として残しておきましょう」とあります。
alert-<%= message_type %>
なるクラスの意味
上述「flash変数の内容をWebサイト全体にわたって表示できるようにするための(仮)コード」の中には、以下のようなコードがあります。
alert-<%= message_type %>
これは、「埋め込みRubyによって、適用するCSSのクラスをメッセージの種類により変更する」というコードです。例えば、:success
キーのメッセージが表示される場合であれば、適用されるCSSクラスは以下のようになります。
alert-success
「:success
はシンボルであるが、テンプレートに反映させる際には、埋め込みRubyが自動的に"success"
という文字列に変換している」という点に注意が必要です。
このような実装により、以下の挙動を実現することができます。
- キーの内容により、異なったCSSクラスを適用させる
- 結果として、メッセージの種類によってスタイルを動的に変更させることができる
- 例えば、「ログインに失敗した旨を示すメッセージを
flash[:danger]
で表示し、その際に自動的にalert-danger
というクラスを適用させる」など
Bootstrap CSSにおける、alert-
から始まる4つのクラス
BootstrapのCSSでは、alert-
から始まるクラスが以下4つ定義されています。
alert-success
alert-info
alert-warning
alert-danger
全体としてどういう挙動になるか
flash[:success] = "Welcome to the Sample App!"
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
上述のコードを踏まえると、当該部分に対して最終的に出力されるHTMLは以下のようになります。
<div class="alert alert-success">Welcome to the Sample App!</div>
flash
変数の内容をWebサイトのレイアウトに追加する
<!DOCTYPE html>
<html>
...略
<body>
<%= render 'layouts/header' %>
<div class="container">
+ <% flash.each do |message_type, message| %>
+ <div class="alert alert-<%= message_type %>"><%= message %></div>
+ <% end %>
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
演習 - flash
1. コンソールに移り、文字列内の式展開 (4.2.2) でシンボルを呼び出してみましょう。例えば"#{:success}"
といったコードを実行すると、どんな値が返ってきますか? 確認してみてください。
# rails console --sandbox
>> "#{:success}"
=> "success"
>> "#{:danger}"
=> "danger"
2. 先ほどの演習で試した結果を参考に、リスト 7.30のflashはどのような結果になるか考えてみてください。
前提として、以下のようなコードを考えます。
<% flash = { success: "It worked!", danger: "It failed." } %>
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
最終的に出力されるHTMLは以下のようになるはずです。
<div class="alert alert-success">It worked!</div>
<div class="alert alert-danger">It failed.</div>
実際のユーザー登録
データベースの内容をリセットする
RailsのActive Recordのマイグレーション機能により、データベースの内容をリセットすることが可能です。データベースの内容をリセットするためのコマンドは、rails db:migrate:reset
です。実行すると、以下のようなログと共に、データベースの内容がリセットされます。
# rails db:migrate:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
== 20190928080951 CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0119s
== 20190928080951 CreateUsers: migrated (0.0122s) =============================
== 20191010034159 AddIndexToUsersEmail: migrating =============================
-- add_index(:users, :email, {:unique=>true})
-> 0.0133s
== 20191010034159 AddIndexToUsersEmail: migrated (0.0136s) ====================
== 20191013040411 AddPasswordDigestToUsers: migrating =========================
-- add_column(:users, :password_digest, :string)
-> 0.0134s
== 20191013040411 AddPasswordDigestToUsers: migrated (0.0137s) ================
ログを見るに、以下のような動作が行われているようですね。
- 既存テーブルの削除
- 新規テーブルの作成
- 既存のマイグレーションに基づき、列情報も自動生成される
- インデックスの定義
- パスワードダイジェスト列の生成
Railsチュートリアルにおける今回の事例は、「Usersコントローラのuser.save
コマンドが実行された回数が学習者ごとに異なる可能性があり、RDBの内容の違いを吸収するためにデータベースの内容をリセットする」という趣旨のものです。
有効なユーザー情報を登録する
早速、ユーザー登録フォームに有効なユーザー情報を入力し、最初のユーザーを作成してみましょう。Railsチュートリアル本文にならい、以下の内容でユーザーを作成してみます。
- Name: Rails Tutorial
- Email: example@railstutorial.org
- PasswordおよびConfirmationは任意
上記はユーザー情報作成完了時の画面です。ユーザー登録の成功を示すフラッシュメッセージもきちんと表示されていますね。なお、Bootstrap CSSにおいて、success
クラスの背景色・文字色は、上記スクリーンショットのように爽やかな緑色です。
ユーザー情報ページを再読み込みすると、今後はフラッシュメッセージは表示されなくなります。上記スクリーンショットは、再読み込み時のものです。
演習 - 実際のユーザー登録
1. Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、リスト 7.32のようになるはずです。
# rails console
>> User.count
(3.4ms) SELECT COUNT(*) FROM "users"
=> 1
>> User.find_by(email: "example@railstutorial.org")
User Load (3.9ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "example@railstutorial.org"], ["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2019-10-22 22:46:59", updated_at: "2019-10-22 22:46:59", password_digest: "$2a$10$j21OfGX82PY0/BqDcapJmeeo/xaVKgSQ9pEZD8hAp4B...">
RDBにも新しく作成されたユーザーの情報が正しく反映されているようです。
2. 自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください。
まず、ユーザー登録フォームの[Create my account]ボタンをクリックしたところからのrails server
のログを示します。
Started POST "/signup" for 172.17.0.1 at 2019-10-22 22:55:21 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"iAAnpc1ZKq9VuKfKkDNV0dVc4M8reSqR57fJU+5zPp8o8FsU4a2WCdXm++5dmoqwagJOnOJWHnu3mt+IIQCS1Q==", "user"=>{"name"=>"Hoge Hoge", "email"=>"[Gravatar登録済みの自分のメールアドレス]", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
(0.1ms) begin transaction
User Exists (7.4ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "[Gravatar登録済みの自分のメールアドレス]"], ["LIMIT", 1]]
SQL (20.8ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?) [["name", "Hoge Hoge"], ["email", "[Gravatar登録済みの自分のメールアドレス]"], ["created_at", "2019-10-22 22:55:21.433059"], ["updated_at", "2019-10-22 22:55:21.433059"], ["password_digest", "$2a$10$GnOotTswgiIehevLmmlDV.XvhMgBXN67XAZp2KvTxip9WtDVsyo0."]]
(23.3ms) commit transaction
Redirected to http://localhost:8080/users/2
Completed 302 Found in 143ms (ActiveRecord: 51.7ms)
最終的にはどうなるでしょうか。
上記スクリーンショットは、ユーザー登録が正常終了した直後のものです。Gravatarに登録したプロフィール画像がきちんと表示されています。
成功時のテスト
今度は「ユーザー登録フォームにおいて、有効なユーザー情報が送信された場合」に対するテストを書いていきます。今回の主題は「データベースの内容が正しいかを検証することにより、有効なユーザー情報に対して正しくユーザーが作成されたことを確認する」というものです。
assert_difference
メソッド
ユーザー登録失敗時のテストに登場したassert_no_difference
と対になるメソッドです。メソッドの使い方はassert_difference
と同様です。
- 第1引数に文字列をとる
- 今回の事例では、
'User.count'
を第1引数とする
- 今回の事例では、
- 第2引数には、比較した結果の差異をとる
- 今回は1とする
- ブロックをとり、ブロックの実行前と実行後で第1引数の文字列が指す要素の値を比較する
assert_difference 'User.count', 1 do
post users_path, ...
end
成功時のテストのコード
class UsersSignupTest < ActionDispatch::IntegrationTest
...略
+
+ test "valid signup information" do
+ get signup_path
+ assert_difference 'User.count', 1 do
+ post users_path, params: { user: { name: "Example User",
+ email: "user@example.com",
+ password: "password",
+ password_confirmation: "password"} }
+ end
+ follow_redirect!
+ assert_template 'users/show'
+ end
end
この時点でテストは通ります。
# rails test integrate
Running via Spring preloader in process 137
Started with run options --seed 37813
21/21: [=================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.48632s
21 tests, 49 assertions, 0 failures, 0 errors, 0 skips
follow_redirect!
について
follow_redirect!
assert_template 'users/show'
assert_difference
ブロックの後に、follow_redirect!
というコードがあります。
follow_redirect!
は、「リダイレクトを伴うテストにおいて、リダイレクト先の処理を明示的に実行する」というメソッドです。今回は、「POST
リクエストの実行結果がリダイレクトになる1ので、users/show
テンプレートが表示されている」目的でfollow_redirect!
メソッドを用いています。
!
がつくだけに、follow_redirect
というメソッドも存在すると考えられます。Google検索等してみたのですが、なぜ!
がつくかについての情報は見つけられませんでした。ただ、「follow_redirect
というメソッドは、Rails 1.1.6から2.1.0まで存在したものの、以降廃止されて現在に至っている」のだそうです。
users/show
テンプレートが正しく表示されることの確認
assert_template 'users/show'
users/show
テンプレートが正しく表示されるには、以下の条件が全て満足される事が必要になります。
- Userのルーティングが正しく実装されている
- Userの
show
アクションが正しく動いている -
show.html.erb
ビューが正しく実装されている
テストの全体像まとめ
今回実装したテストは、以下の事柄について確認しています。
- 正しいユーザー登録データに対し、
post
メソッドが正しく動作する - ユーザー登録の内容がRDBに反映される
- ユーザー登録後、登録前に比べてRDBのレコード数が1つ増えているかどうか
-
POST
メソッド完了時のリダイレクトが正しく実装されている -
users/show
テンプレートが正しく表示される
「ユーザー登録成功」というフローで必要とされる機能がカバーされていますね。こういうときに統合テストは便利なのです。
演習 - 成功時のテスト
1. 7.4.2で実装したflashに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.34に最小限のテンプレートを用意しておいたので、参考にしてください (FILL_INの部分を適切なコードに置き換えると完成します)。
ちなみに、テキストに対するテストは壊れやすいです。文量の少ない
flash
のキーであっても、それは同じです。筆者の場合、flash
が空でないかをテストするだけの場合が多いです。
Railsチュートリアル本文にならい、flash
が空でないかをテストするだけの実装とします。
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
...略
test "valid signup information" do
get signup_path
assert_difference 'User.count', 1 do
post users_path, params: { user: { name: "Example User",
email: "user@example.com",
password: "password",
password_confirmation: "password"} }
end
follow_redirect!
assert_template 'users/show'
+ assert_not flash.empty?
end
end
現時点の状態からapp/controllers/users_controller.rb
に手を加えなければ、テストは通ります。
# rails test integrate
Running via Spring preloader in process 189
Started with run options --seed 27734
21/21: [=================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.08653s
21 tests, 50 assertions, 0 failures, 0 errors, 0 skips
一方、app/controllers/users_controller.rb
でflash[:success]
以下の行をコメントアウトすると、テストが通らなくなります。
class UsersController < ApplicationController
...略
def create
@user = User.new(user_params)
if @user.save
- flash[:success] = "Welcome to the Sample App!"
+ #flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
...略
end
# rails test integrate
Running via Spring preloader in process 202
Started with run options --seed 46053
FAIL["test_valid_signup_information", UsersSignupTest, 1.8803565000016533]
test_valid_signup_information#UsersSignupTest (1.88s)
Expected true to be nil or false
test/integration/users_signup_test.rb:33:in `block in <class:UsersSignupTest>'
21/21: [=================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.38272s
21 tests, 50 assertions, 1 failures, 0 errors, 0 skips
2.1. 本文中でも指摘しましたが、flash用のHTML (リスト 7.31) は読みにくいです。より読みやすくしたリスト 7.35のコードに変更してみましょう。
なお、このコードでは、Railsのcontent_tagというヘルパーを使っています。
<!DOCTYPE html>
<html>
...略
<body>
...略
<div class="container">
<% flash.each do |message_type, message| %>
- <div class="alert alert-<%= message_type %>"><%= message %></div>
+ <% content_tag(:div, message, class: "alert alert-#{message_type}") %>
<% end %>
...略
</div>
</body>
</html>
「読みにくい」というか、「RubyのコードとHTMLのコードが混在するのはよろしくない」という事情もあります。
Railsのcontent_tag
ヘルパーにより、RubyのコードのみでHTMLを構成できるようになりました。
2.2. 変更が終わったらテストスイートを実行し、正常に動作することを確認してください。
# rails test integrate
Running via Spring preloader in process 215
Started with run options --seed 26929
21/21: [=================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.10806s
21 tests, 50 assertions, 0 failures, 0 errors, 0 skips
問題なくテストが通っていますね。
3. リスト 7.28のリダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。
class UsersController < ApplicationController
...略
def create
@user = User.new(user_params)
if @user.save
flash[:success] = "Welcome to the Sample App!"
- redirect_to @user
+ #redirect_to @user
else
render 'new'
end
end
...略
end
このコードに対するテストの結果は以下のようになります。
# rails test integrate
Running via Spring preloader in process 228
Started with run options --seed 63745
ERROR["test_valid_signup_information", UsersSignupTest, 3.581053300000349]
test_valid_signup_information#UsersSignupTest (3.58s)
RuntimeError: RuntimeError: not a redirect! 204 No Content
test/integration/users_signup_test.rb:31:in `block in <class:UsersSignupTest>'
21/21: [=================================] 100% Time: 00:00:03, Time: 00:00:03
Finished in 3.66224s
21 tests, 48 assertions, 0 failures, 1 errors, 0 skips
テストが通らなくなりました。「RuntimeError: not a redirect! 204 No Content」というエラーメッセージがポイントですね。
4. リスト 7.28で、@user.save
の部分をfalse
に置き換えたとしましょう (バグを埋め込んでしまったと仮定してください)。このとき、assert_difference
のテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。
assert_difference 'User.count', 1 do
#...略
end
@user.save
が実行されない場合、RDBにレコードは追加されません。結果、User.count
の値は変わらなくなります。「@user.save
の実行後は、実行前と比較してUser.count
の値が1増えていなければならない」という条件に反するので、この時点でテストが失敗となります。
実際にやってみるとどうなるでしょうか。
class UsersController < ApplicationController
...略
def create
@user = User.new(user_params)
- if @user.save
+ if false
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
...略
end
テスト結果はこのようになります。
# rails test integrate
Running via Spring preloader in process 241
Started with run options --seed 8272
FAIL["test_invalid_signup_information", UsersSignupTest, 1.5347172000001592]
test_invalid_signup_information#UsersSignupTest (1.54s)
Expected at least 1 element matching "div#error_explanation", found 0..
Expected 0 to be >= 1.
test/integration/users_signup_test.rb:15:in `block in <class:UsersSignupTest>'
FAIL["test_valid_signup_information", UsersSignupTest, 1.5782804999998916]
test_valid_signup_information#UsersSignupTest (1.58s)
"User.count" didn't change by 1.
Expected: 1
Actual: 0
test/integration/users_signup_test.rb:25:in `block in <class:UsersSignupTest>'
21/21: [=================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.10267s
21 tests, 44 assertions, 2 failures, 0 errors, 0 skips
あっ、「Users.errors
に何も代入されないため、new
がレンダリングされたときにapp/views/shared/_error_messages.html.erb
の内容がレンダリングされない。結果、IDがerror_explanation
となるdiv
要素が存在しないため、その点でテストが失敗する」というのもありました。
-
HTTPのステータスコードは「302 Found」が返ってきます。HTTPの仕様としては「303 See Other」が正しい応答なのだそうですが、後方互換性等の観点から、このような場合には302を返すのがデファクトスタンダードになっているそうです。 ↩