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

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

### テスト対象

該当するコントローラ  | 該当するアクション
:--|:--
UsersController  |  following, followers

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

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

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


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

- フォロー／フォロワー のユーザ情報の表示

- ※ 未ログインの場合（ページ保護）／ユーザ削除権限（admin権限）のテストは `authorization_spec.rb` （別ファイル）にて作成
  [フィーチャスペック編 authorization_spec](https://qiita.com/shu130/items/700379de730d34ef3849)

```ruby:users_following_spec.rb
# spec/features/users_following_spec.rb
RSpec.feature "UsersFollowing", type: :feature do

  # 未ログイン場合のスペックは、
  # authorization_spec.rb にて（別ファイル）作成

  # フォロー／フォロワー
  describe "following/followers method"
    # 自分がフォロー中のユーザのページ
    describe "following page"
      # ユーザ情報の表示が正しいこと
      it_behaves_like "have user infomation"
    # 自分をフォローしているユーザのページ
    describe "followers page"
      # ユーザ情報の表示が正しいこと
      it_behaves_like "have user infomation"

end
```

#### shared_examples の作成

- ##### it_behaves_like "have user infomation"

  ```ruby:shared_examples.rb
  # spec/support/shared_examples.rb
    # ユーザ情報
    # top#home, users#following, users#followers
    shared_examples_for "have user infomation" do
      it { expect(page).to have_css('img.gravatar') }
      it { expect(page).to have_css('h1', text: user.name) }
      it { expect(page).to have_text("Microposts") }
      it { expect(page).to have_link("view my profile", href: user_path(user)) }
    end
  ```

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

```ruby:users_following_spec.rb
# spec/features/users_follow_spec.rb

require 'rails_helper'

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

  include SupportModule
  include_context "setup"

  subject { page }

  # 未ログインの場合のテストは、
  # authorization_spec.rb にて（別ファイル）実施

  describe "following/followers method" do
    describe "following page" do
      before do
        login_as(user)
        click_link "following"
      end
      it_behaves_like "have user infomation"
    end
    describe "followers page" do
      before do
        login_as(user)
        click_link "following"
      end
      it_behaves_like "have user infomation"
    end
  end
end
```

### 実行結果 1/2

```test
$ bin/rspec spec/features/users_follow_spec.rb -e "following page" -e "followers page"

UsersFollow
  following/followers method
    following page
      behaves like have user infomation
        should have visible css "img.gravatar"
        should have visible css "h1" with text "Example user"
        should text "Microposts"
        should have visible link "view my profile" with href "/users/5"
    followers page
      behaves like have user infomation
        should have visible css "img.gravatar"
        should have visible css "h1" with text "Example user"
        should text "Microposts"
        should have visible link "view my profile" with href "/users/5"

Finished in 5.6 seconds (files took 2.16 seconds to load)
8 examples, 0 failures

```


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



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

```ruby:users_following_spec.rb
# spec/features/users_follow_spec.rb
RSpec.feature "UsersFollowing", type: :feature do

  # フォロー／フォロワー
  describe "following/followers method"

    # （省略）

    # ページネーション
    describe "pagination"
      # ユーザ情報が表示されている
      scenario "list each following"
      # ユーザ情報が表示されている
      scenario "list each followers"

end
```

#### サンプルデータのセットアップ

```ruby:users_following_spec.rb
# spec/features/users_follow_spec.rb
  describe "following/followers method" do
    # セットアップ
    before do
      users # => shared_context 内で定義 create_list(:other_user, 30)
      users.each do |u|
         user.follow(u) # => 自分が ３０人をフォローする
         u.follow(user) # => 他人の ３０人にフォローされる
      end
      user.follow(other_user) # => 自分が他人（1人）をフォロー
      other_user.follow(user) # => 他人が自分（1人）をフォロー
    end
    # （セットアップの確認：３１人をフォローしている）
    it { expect(user.following.count).to eq 31 }
    # （セットアップの確認：３１人にフォローされている）
    it { expect(other_user.following.count).to eq 1 }
    it { expect(user.followers.count).to eq 31 }
  end
```

#### コンソールで確認

- `$rails console test --sandbox`

  ```test
  （抜粋）
  $ rails c test -s
  [1] pry(main)>
  [2] pry(main)> users = FactoryBot.create_list(:other_user, 3)
    id: 5,
    name: "Jess Witting",
    email: "everettkunze@wintheiser.com",

    id: 6,
    name: "Tula Reilly",
    email: "sashagleason@leuschke.co",

    id: 7,
    name: "Cori Emard",
    email: "rustywill@mayert.com",

  [3] pry(main)>
  [4] pry(main)>
  [5] pry(main)> me = FactoryBot.create(:user)
   id: 8,
   name: "Example user",
   email: "user@example.com",

  [8] pry(main)>
  [9] pry(main)> users.each do |u|
  [9] pry(main)*   me.follow(u)
  [9] pry(main)*   u.follow(me)
  [9] pry(main)* end

  （抜粋）自分が フォロー中のユーザ

    [["follower_id", 8], ["followed_id", 5], ["created_at", "2018-08-07 02:07:11.485562"], ["updated_at", "2018-08-07 02:07:11.485562"]]

    [["follower_id", 8], ["followed_id", 6], ["created_at", "2018-08-07 02:07:11.512676"], ["updated_at", "2018-08-07 02:07:11.512676"]]

    [["follower_id", 8], ["followed_id", 7], ["created_at", "2018-08-07 02:07:11.536817"], ["updated_at", "2018-08-07 02:07:11.536817"]]

  （抜粋）自分を フォローしているのユーザ

    [["follower_id", 5], ["followed_id", 8], ["created_at", "2018-08-07 02:07:11.499536"], ["updated_at", "2018-08-07 02:07:11.499536"]]

    [["follower_id", 6], ["followed_id", 8], ["created_at", "2018-08-07 02:07:11.527219"], ["updated_at", "2018-08-07 02:07:11.527219"]]

    [["follower_id", 7], ["followed_id", 8], ["created_at", "2018-08-07 02:07:11.546484"], ["updated_at", "2018-08-07 02:07:11.546484"]]
  ```

#### 実行結果

- サンプルデータのセットアップ

  ```test
  $ bin/rspec spec/features/users_follow_spec.rb

  UsersFollow
    following/followers method
      should eq 31
      should eq 1
      should eq 31

  Finished in 3.87 seconds (files took 2.08 seconds to load)
  3 examples, 0 failures
  ```

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

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

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

  include SupportModule
  include_context "setup"

  subject { page }

  # 未ログインの場合のテストは、
  # authorization_spec.rb にて（別ファイル）実施

  describe "following/followers method" do
    before do
      users # => shared_context 内で定義 create_list(:other_user, 30)
      users.each do |u|
         user.follow(u) # => 自分が ３０人をフォローする
         u.follow(user) # => 他人の ３０人にフォローされる
      end
      user.follow(other_user) # => 自分が他人（1人）をフォロー
      other_user.follow(user) # => 他人が自分（1人）をフォロー
    end

    # （セットアップの確認：３１人をフォローしている）
    it { expect(user.following.count).to eq 31 }
    # （セットアップの確認：３１人にフォローされている）
    it { expect(other_user.following.count).to eq 1 }
    it { expect(user.followers.count).to eq 31 }

    # （省略）

    describe "pagination" do
      before { login_as(user) }
      scenario "list each following" do
        # login_as(user)
        click_link "following"
        user.following.paginate(page: 1).each do |u|
          expect(page).to have_css("li", text: u.name)
          expect(page).to have_link(u.name, href: user_path(u))
        end
      end
      scenario "list each followers" do
        # login_as(user)
        click_link "followers"
        user.followers.paginate(page: 1).each do |u|
          expect(page).to have_css("li", text: u.name)
          expect(page).to have_link(u.name, href: user_path(u))
        end
      end
    end
  end
end
```

### 実行結果 2/2

```test
$ bin/rspec spec/features/users_follow_spec.rb -e "pagination"

UsersFollow
  following/followers method
    pagination
      list each following
      list each followers

Finished in 6.39 seconds (files took 1.98 seconds to load)
2 examples, 0 failures

```


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



### 未ログインの場合（ページ保護）のスペック

- #### authorization_spec.rb （別ファイル）にて作成

#### アウトラインの作成

```ruby:authorization_spec.rb
# spec/features/authorization_spec.rb
RSpec.feature "Authorization", type: :feature do

  # ページ保護（アクセス権限）
  describe "in UsersController"
    describe "login is necessary"
      # 未ログインの場合
      context "when non-login"
        describe "edit"
          # ログインメッセージが出力されること
          it "should have error_messages 'Please log in'"
          # ログインページへリダイレクトされること
          it "should have redirect to '/login'"
        describe "update"
          it "should have error_messages 'Please log in'"
          it "should have redirect to '/login'"
end
```

#### shared_examples の作成

- ##### ログインメッセージ出力・ログインページへリダイレクトは、一般化して使い回せるようにしてみる

- spec側でブロックをオブジェクト化(`Proc.new { }`)して `subject { }` に定義し、shared_examples側で、`subject.call` して呼び出す

  ```ruby:shared_examples.rb
  # spec/support/shared_examples.rb

    # 成功メッセージ
    # flash[:success]
    shared_examples_for "success message" do |msg|
      it { subject.call; expect(flash[:success]).to eq msg }
    end

    # 失敗メッセージ
    # flash[:danger]
    shared_examples_for "error message" do |msg|
      it { subject.call; expect(flash[:danger]).to eq msg }
    end

    # リダイレクト
    # redirect to path
    shared_examples_for "redirect to path" do |path|
      it { subject.call; expect(response).to redirect_to path }
    end
  ```

#### スペック作成
<!--
- #### 未ログインの場合（ページ保護）のスペックは authorization_spec.rb （別ファイル）にて作成
 -->
```ruby:authorization_spec.rb
# spec/features/authorization_spec.rb
RSpec.feature "Authorization", type: :feature do

  include SupportModule
  include_context "setup"

  # アクセス権限
  describe "in UsersController", type: :request do
    # 未ログインの場合 （before_action のテスト）
    describe "login is necessary" do
      context "when non-login" do
        describe "following" do
          subject { Proc.new { get following_user_path(user) } }
          it_behaves_like "error flash", "Please log in"
          it_behaves_like "redirect to path", "/login"
        end
        describe "followers" do
          subject { Proc.new { get followers_user_path(user) } }
          it_behaves_like "error flash", "Please log in"
          it_behaves_like "redirect to path", "/login"
        end
      end
    end
  end
end
```

#### 実行結果

```test
$ bin/rspec spec/features/authorization_spec.rb -e "following" -e "followers"

Authorization
  in UsersController
    login is necessary
      when non-login
        following
          behaves like error flash
            should eq "Please log in"
          behaves like redirect to path
            should redirect to "/login"
        followers
          behaves like error flash
            should eq "Please log in"
          behaves like redirect to path
            should redirect to "/login"

Finished in 1.86 seconds (files took 1.95 seconds to load)
4 examples, 0 failures

```

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

### 参考

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

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

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

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