ユーザー認証のテスト(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)>'

fail

失敗を引き起こしている箇所が、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 の華、「モック」が登場します。では、また。