LoginSignup
1
0

More than 3 years have passed since last update.

Railsチュートリアル 第7章 ユーザー登録 - ユーザー登録成功

Posted at

ユーザー登録に成功した画面のモックアップ

ユーザー登録に失敗した場合の挙動は、ここまでの学習で実装が完了しました。

ユーザー登録に成功した画面のモックアップ - ユーザー登録に成功したら、当該リンク先のような画面を出力するように実装していきます。

登録フォームの完成

現状、有効な情報で登録操作を行うと何が起こるか

現状では、ユーザー登録フォームに有効な情報で登録操作を行っても、元の画面に戻されてしまいます。

スクリーンショット 2019-10-22 18.29.55.png

このときの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」というのがポイントですね。実際に発生しているのは以下の事態です。

  1. Railsはデフォルトのアクションに対応するビューを表示しようとする
  2. createアクションに対応するビューのテンプレートは現状存在しない
  3. 元の画面に戻されてしまう

登録内容は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です。

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)

最終的には、以下のような画面が表示されます。

スクリーンショット 2019-10-22 18.47.20.png

先ほど作成したユーザー情報の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を変更します。

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内です。

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サイトのレイアウトに追加する

app/views/layouts/application.html.erb
  <!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チュートリアル本文にならい、以下の内容でユーザーを作成してみます。

スクリーンショット 2019-10-23 7.47.16.png

上記はユーザー情報作成完了時の画面です。ユーザー登録の成功を示すフラッシュメッセージもきちんと表示されていますね。なお、Bootstrap CSSにおいて、successクラスの背景色・文字色は、上記スクリーンショットのように爽やかな緑色です。

スクリーンショット 2019-10-23 7.49.54.png

ユーザー情報ページを再読み込みすると、今後はフラッシュメッセージは表示されなくなります。上記スクリーンショットは、再読み込み時のものです。

演習 - 実際のユーザー登録

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)

最終的にはどうなるでしょうか。

スクリーンショット 2019-10-23 7.55.39.png

上記スクリーンショットは、ユーザー登録が正常終了した直後のものです。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

成功時のテストのコード

test/integration/users_signup_test.rb
  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が空でないかをテストするだけの実装とします。

test/integration/users_signup_test.rb
  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.rbflash[:success]以下の行をコメントアウトすると、テストが通らなくなります。

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!"
+       #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というヘルパーを使っています。

app/views/layouts/application.html.erb
  <!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のリダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。

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
+       #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増えていなければならない」という条件に反するので、この時点でテストが失敗となります。

実際にやってみるとどうなるでしょうか。

app/controllers/users_controller.rb
  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要素が存在しないため、その点でテストが失敗する」というのもありました。


  1. HTTPのステータスコードは「302 Found」が返ってきます。HTTPの仕様としては「303 See Other」が正しい応答なのだそうですが、後方互換性等の観点から、このような場合には302を返すのがデファクトスタンダードになっているそうです。 

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0