Railsチュートリアル  テストをRSpecで実施   【フィーチャスペック編 users_login_spec 3/7】 

<!--
# RailsチュートリアルテストをRSpecでやってみる 【フィーチャースペック編 users_login_spec 3/7】
-->
<!-- ## フィーチャースペック編 users_login_spec 3/7 -->

### テスト対象

該当するコントローラ  | 該当するアクション
:--|:--
SessionsController  |  new, create, destroy

### 環境
[Userモデル 単体テスト編 1/3](https://qiita.com/shu130/items/57233462258d42566f18) に記載

#### フォルダ、使用ファイル

種類  |  ファイル名
:--|:--
スペック  |  spec/features/users_login_spec.rb
サポートモジュール  |  spec/support/support_module.rb
shared_examples  |  spec/support/shared_examples.rb
shared_context  |  spec/support/shared_context.rb
ファクトリ（ユーザ）  |  spec/support/factories/users.rb


### アウトライン作成 1/2

```ruby:users_login_spec.rb
# spec/features/users_login_spec.rb
RSpec.feature "UsersLogin", type: :feature do

  # ログインページ
  describe "log in"
    # loginフォームが正しいこと
    it_behaves_like "log in form have right css"
    # 情報が valid
    context "valid info"
      # 成功
      scenario "success log in"
    # 情報が invalid
    context "invalid info"
      # 失敗
      scenario "fail log in"

end
```

### スペック作成 1/2

```ruby:users_login_spec.rb
# spec/features/users_login_spec.rb
require 'rails_helper'

RSpec.feature "UsersLogin", type: :feature do

  include SupportModule
  include_context "setup"

  subject { page }

  describe "log in" do
    before { visit "/login" }
    # loginフォームが正しいこと
    it_behaves_like "login-form have right css"
    # ログイン情報が 有効
    context "valid info" do
      scenario "success log in" do
        visit "/login"
        fill_in_login_form(user) # =>  support_module
        click_button "Log in"
        should have_title(user.name)
        should have_css('h1', text: user.name)
        should have_current_path(user_path(user))
      end
    end
    # ログイン情報が 無効
    context "invalid info" do
      scenario "fail log in" do
        visit login_path
        fill_in_login_form(user, invalid: true) # =>  support_module
        click_button "Log in"
        error_messages "Invalid email/password combination"
        should have_title("Log in")
        should have_css("h1", text: "Log in")
        should have_current_path("/login")
      end
    end
  end
end
```

#### shared_examples の作成

- `it_behaves_like "login-form have right css"` の部分

  ```ruby:shared_examples.rb
  # spec/support/shared_examples.rb
    # login form
    # sessions#new
    shared_examples_for "login-form have right css" do
      it { expect(page).to have_css('label', text: 'Email') }
      it { expect(page).to have_css('label', text: 'Password') }
      it { expect(page).to have_css('input#session_email') }
      it { expect(page).to have_css('input#session_password') }
      it { expect(page).to  have_css('input#session_remember_me[type="checkbox"]') }
      it { expect(page).to have_css('label.checkbox.inline', text: 'Remember me') }
      it { expect(page).to have_button('Log in') }
    end
  ```

#### SupportModule, shared_context, ファクトリ

- SupportModule

  - `fill_in_login_form(user)` （ログイン情報が 有効／無効）
  - `error_messages "Invalid email/password combination"` の部分

  ```ruby:support_module.rb
  # spec/support/support_module.rb
  module SupportModule
    def fill_in_login_form(user, option = { invalid: false })
      if option[:invalid]
        fill_in "Email",        with: ""
        fill_in "Password",     with: ""
      else
        fill_in "Email",        with: user.email
        fill_in "Password",     with: user.password
      end
    end
  end
  ```
  ```ruby::support_module.rb
  module SupportModule
    def error_messages(msg = "")
      # should have_css('div#error_explanation')
      # should have_css('div.alert.alert-danger') if msg.empty? and return
      if msg.empty?
        should have_css('div.alert.alert-danger')
      else
        should have_css('div.alert.alert-danger', text: msg)
      end
    end
  end
  ```

- shared_context

  ```ruby:shared_context
  # spec/support/shared_context.rb
  RSpec.shared_context "setup" do
    # 遅延評価、呼ばれた時にDB保存される
    let(:user) { create(:user) }
  end
  ```

- ファクトリ（ユーザ）

  ```ruby:users.rb
  # spec/support/factories/users.rb
  FactoryBot.define do
    # 自分
    # factory [任意のファクトリ名], class: [クラス名]
    factory :user, class: User do
      name     "Example user"
      email    "user@example.com"
      password              "foobar"
      password_confirmation "foobar"
      admin false
  end
  ```

### 実行結果 1/2

```test
$ bin/rspec spec/features/users_login_spec.rb -e "log in"

UsersLogin
  log in
    behaves like login-form have right css
      should have visible css "label" with text "Email"
      should have visible css "label" with text "Password"
      should have visible css "input#session_email"
      should have visible css "input#session_password"
      should have visible css "input#session_remember_me[type=\"checkbox\"]"
      should have visible css "label.checkbox.inline" with text "Remember me"
      should have visible button "Log in"
    valid info
      success log in
    invalid info
      fail log in

Finished in 4.9 seconds (files took 2.28 seconds to load)
9 examples, 0 failures

```


<!-- 1/2 -->
--------------------------------



### アウトライン作成 2/2

```ruby:users_login_spec.rb
# spec/features/users_login_spec.rb
RSpec.feature "UsersLogin", type: :feature do

  # （省略）

  # ログアウト
  describe "log out"
    # ログアウトが正常にできること
    describe "success log out"
      # 同じサイトを 複数tab/window で開いている状態をシュミレート
      context "when open in some browser-tab(or window)"
      # "Log out" をクリック １回目をシュミレート
      scenario "1st time log out"
      # "Log out" をクリック ２回目をシュミレート
      scenario "2nd time log out"

end
```

### スペック作成 2/2

```ruby:users_login_spec.rb
# spec/features/users_login_spec.rb
require 'rails_helper'

RSpec.feature "UsersLogin", type: :feature do

  include SupportModule
  include_context "setup"

  subject { page }

  # （省略）

  # ログアウト
  describe "log out" do
    # ログアウトが正常にできること
    describe "success log out" do
      # 同じサイトを 複数tab/window で開いている状態をシュミレート
      context "when open in some browser-tab(or window)" do
        # "Log out" をクリック １回目をシュミレート
        scenario "1st time log out" do
          login_as(user)
          click_link "Log out"
          should have_current_path(root_path)
        end
        # "Log out" をクリック ２回目をシュミレート
        # HTTPリクエストを直接送るので、type: :request オプションをつける
        scenario "2nd time log out", type: :request do
          delete logout_path(user)
          should have_current_path(root_path)
        end
      end
    end
  end
end
```

#### SupportModule

- `login_as(user)` の部分

  ```ruby::support_module.rb
  # spec/support/support_module.rb
  module SupportModule
    def login_as(user)
      visit root_path
      click_link "Log in"
      fill_in "Email",    with: user.email
      fill_in "Password", with: user.password
      click_button "Log in"
    end
  end
  ```

### 実行結果 2/2

```test
$ bin/rspec spec/features/users_login_spec.rb -e "log out"

UsersLogin
  log out
    success log out
      when open in some browser-tab(or window)
        1st time log out
        2nd time log out

Finished in 5.62 seconds (files took 2.19 seconds to load)
2 examples, 0 failures

```


<!-- 2/2 -->
--------------------------------

### 参考

- [Railsチュートリアル Rails4.0(第2版)](https://railstutorial.jp/chapters/modeling-users?version=4.0#top)

- [(書籍)leanpub - Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp/)

--------------------------------

### 参考

- [Railsチュートリアル Rails4.0(第2版)](https://railstutorial.jp/chapters/modeling-users?version=4.0#top)

- [(書籍)leanpub - Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp/)

--------------------------------

### 続く
[フィーチャスペック編 users_profile_spec 4/7](https://qiita.com/shu130/items/847c75f0a0bf90c28cf3)