ユーザー認証のテスト(2) -- アクションの仮実装
2013/09/01
前回に引き続き、テスト駆動開発(TDD)の方式でログイン・ログアウト機能の実装を続けます。
テストコードのおさらい
ユーザー認証機能のテスト spec/features/login_and_logout_spec.rb
は現在このような形をしています。
require 'spec_helper' describe 'ログイン' do specify 'ユーザー認証成功' do visit root_path within('form#new_session') do fill_in 'username', with: 'taro' fill_in 'password', with: 'correct_password' click_button 'ログイン' end expect(page).not_to have_css('form#new_session') end specify 'ユーザー認証失敗' do visit root_path within('form#new_session') do fill_in 'username', with: 'taro' fill_in 'password', with: 'wrong_password' click_button 'ログイン' end expect(page).to have_css('p.alert', text: 'ユーザー名またはパスワードが正しくありません。') expect(page).to have_css('form#new_session') end end
いまテストが落ちているのは、9行目と19行目です。「ログイン」ボタンをクリックするとフォームデータが Rails アプリケーションに対して送信されるのですが、まだ送信先のアクションが存在しないので、そこでエラーが発生します。
ルーティング、そしてアクションの仮実装
まず、ユーザー認証を行うアクション、URL、HTTP メソッドの種類を決めましょう。特に決まりはありませんので、読者の皆さんの慣習に従ってください。ここでは、アクションを sessions#create
とし、POST
メソッドで /login
にアクセスすることにします。
次に、この決定事項をアプリケーション本体のソースコードに書き込んでいきます。
config/routes.rb
を修正:
Sinope::Application.routes.draw do post 'login' => 'sessions#create', as: :login root to: 'top#index' end
2行目末尾の as: :login
は、/login
という URL パスを :login
という名前で参照できるようにするためのオプションです。
app/controllers/sessions_controller.rb
を新規作成:
class SessionsController < ApplicationController def create redirect_to :root end end
まだアクションの中身は実装しません。ただし、空のままにしておくと sessions/create
というテンプレートが存在しないという例外が発生して前に進まないので、とりあえずトップページにリダイレクションしておきます。
app/views/top/_login_form.html.erb
を修正:
<%= form_tag :login, id: 'new_session' do %> <div> <%= label_tag 'username', 'ユーザー名' %> <%= text_field_tag 'username' %> </div> <div> <%= label_tag 'password', 'パスワード' %> <%= password_field_tag 'password' %> </div> <div> <%= submit_tag 'ログイン' %> </div> <% end %>
変更箇所は1行目です。form_tag
の第1引数を ''
から :login
に変更しました。
この状態でテストを実行すると、次のような結果になります:
Failures: 1) ログイン ユーザー認証成功 Failure/Error: expect(page).not_to have_css('form#new_session') Capybara::ExpectationNotMet: expected not to find css "form#new_session", found 1 match: "ユーザー名 パスワード" # ./spec/features/login_and_logout_spec.rb:11:in `block (2 levels) in <top (required)>' 2) ログイン ユーザー認証失敗 Failure/Error: expect(page).to have_css('p.alert', text: 'ユーザー名またはパスワードが正しくありません。') Capybara::ExpectationNotMet: expected to find css "p.alert" with text "ユーザー名またはパスワードが正しくありません。" but there were no matches # ./spec/features/login_and_logout_spec.rb:21:in `block (2 levels) in <top (required)>'
失敗を引き起こしている箇所が、11行目と21行目に進みました。
ユーザー認証失敗シナリオの実装
いよいよ本丸に近づいてきました。
sessions#create
アクションを実装しましょう。
でも、これまでのようには単純ではありません。ユーザー認証が成功する場合と失敗する場合でシナリオが分岐するからです。
両方同時に実装しようとするのは混乱の元。
簡単そうな後者の方からやっつけましょうか。
app/controllers/sessions_controller.rb
を修正:
class SessionsController < ApplicationController def create if false # 未実装 else flash.alert = 'ユーザー名またはパスワードが正しくありません。' end redirect_to :root end end
app/views/layouts/application.html.erb
を修正(<%= render 'layouts/flashes' %>
を追加):
<!DOCTYPE html> <html> <head> <title>Sinope</title> <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> <%= javascript_include_tag "application", "data-turbolinks-track" => true %> <%= csrf_meta_tags %> </head> <body> <%= render 'layouts/flashes' %> <%= yield %> </body> </html>
app/views/layouts/_flashes.html.erb
を新規作成:
<% if flash.alert.present? %> <p class="alert"><%= flash.alert %></p> <% end %> <% if flash.notice.present? %> <p class="notice"><%= flash.notice %></p> <% end %>
これで「ユーザー認証失敗」のエグザンプルが通ります。テストの実行結果は次の通り:
Failures: 1) ログイン ユーザー認証成功 Failure/Error: expect(page).not_to have_css('form#new_session') Capybara::ExpectationNotMet: expected not to find css "form#new_session", found 1 match: "ユーザー名 パスワード" # ./spec/features/login_and_logout_spec.rb:11:in `block (2 levels) in <top (required)>'
今回はここまでとしましょう。
次回は
次回の「ユーザー認証のテスト(3)」では、ついに RSpec の華、「モック」が登場します。では、また。