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

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

### テスト対象

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

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

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

種類  |  ファイル名
:--|:--
スペック  |  spec/features/microposts_pages_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
ファクトリ（マイクロポスト）  |  spec/support/factories/microposts.rb


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

- マイクロポストの作成／削除
- 他人のマイクロポストは削除できない

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

```ruby::microposts_pages_spec.rb
# spec/features/microposts_pages_spec.rb
RSpec.feature "MicropostsPages", type: :feature do

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

  # マイクロポストの作成（create）
  describe "create"
    # valid な情報の場合
    context "valid info"
      # 成功 (increment: 1)
      it_behaves_like "success create micropost"
      # 成功メッセージ
      scenario "have success messages"
    # invalid な情報の場合
    context "invalid info"
      it_behaves_like "fail create micropost"
      # 失敗メッセージ
      scenario "have error messages"
    # マイクロポストの削除（delete）
    describe "destroy"
      # 自分のマイクロポストの場合
      context "attempt to delete my microposts"
        # 成功 (decrement: -1)
        it_behaves_like "success delete micropost"
        # successメッセージが出力されること
        scenario "have success messages"
      # 他人のマイクロポストの場合
      context "attempt to delete other user's microposts"
        # 失敗 (decrement: 0)
        it_behaves_like "fail delete micropost"
end
```

### スペック作成

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

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

  include SupportModule
  include_context "setup"

  subject { page }

  # マイクロポストの作成（create）
  describe "create" do
    before { login_as(user) }
    # valid な情報の場合
    context "valid info" do
      # 成功 (increment: 1)
      it_behaves_like "success create micropost"
    end
    # invalid な情報の場合
    context "invalid info" do
      it_behaves_like "fail create micropost"
    end
  end

  # マイクロポストの削除（delete）
  describe "destroy", type: :request do
    before { login_as(user) }
    # 自分のマイクロポストの場合
    context "attempt to delete my microposts" do
      it_behaves_like "success delete micropost"
    end
    # 他人のマイクロポストの場合
    context "attempt to delete other user's microposts" do
      it_behaves_like "fail delete micropost"
    end
  end
end
```

#### shared_examples の作成

- `it_behaves_like "success create micropost"`
- `it_behaves_like "fail create micropost"`
- `it_behaves_like "success delete micropost"`
- `it_behaves_like "fail delete micropost"`

  ```ruby::shared_examples.rb
  # spec/support/shared_examples.rb
                    # マイクロポストの作成／削除
    # microposts#create
    # 成功
    shared_examples_for "success create micropost" do
      scenario "micropost increment 1" do
        expect {
          visit root_path
          params = post_params
          fill_in "micropost_content", with: params[:content]
          click_button "Post"
        }.to change(Micropost, :count).by(1)
        # 成功メッセージ
        expect(page).to have_css("div.alert.alert-success", text: "Micropost created")
      end
    end
    # 失敗
    shared_examples_for "fail create micropost" do
      scenario "micropost increment 0" do
        expect {
          visit root_path
          fill_in "micropost_content", with: ""
          click_button "Post"
        }.to change(Micropost, :count).by(0)
        # 失敗メッセージ
        expect(page).to have_css("div.alert.alert-danger")
      end
    end

    # microposts#destroy
    # 成功
    shared_examples_for "success delete micropost" do
      before { my_posts }
      scenario "micropost descrement -1" do
        expect {
          visit root_path
          click_link "delete", match: :first
        }.to change(Micropost, :count).by(-1)
        # 成功メッセージ
        expect(page).to have_css("div.alert.alert-success", text: "Micropost deleted")
      end
    end
    # 失敗
    shared_examples_for "fail delete micropost" do
      before { other_posts }
      scenario "micropost descrement 0" do
        expect {
          # リンクが無いので、直接 HTTPリクエストを発行
          delete micropost_path(other_posts.first.id)
        }.to change(User, :count).by(0)
      end
    end
  ```


### 実行結果

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

MicropostPages
  create
    valid info
      behaves like success create micropost
        micropost increment 1
    invalid info
      behaves like fail create micropost
        micropost increment 0
  destroy
    attempt to delete my microposts
      behaves like success delete micropost
        micropost descrement -1
    attempt to delete other user's microposts
      behaves like fail delete micropost
        micropost descrement 0

Finished in 7.13 seconds (files took 2.15 seconds to load)
4 examples, 0 failures

```

<!--  -->
--------------------------------




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

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

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

  # ページ保護（アクセス権限）
  describe "in MicropostsController"
    # マイクロポスト 投稿（作成）／削除
    describe "authorization of create/destroy micropost"
      context "when non-login"
        describe "create"
        describe "destroy"
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
require 'rails_helper'

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

  include SupportModule
  include_context "setup"

  # アクセス権限
  describe "in MicropostsController", type: :request do
    # マイクロポスト 投稿（作成）／削除
    describe "authorization of create/destroy micropost" do
      context "when non-login" do
        describe "create" do
          subject { Proc.new { post microposts_path, params: post_params } }
          it_behaves_like "error flash", "Please log in"
          it_behaves_like "redirect to path", "/login"
        end
        describe "destroy" do
          subject { Proc.new { delete micropost_path(my_post.id) } }
          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 "authorization of create/destroy micropost"

Authorization
  in MicropostsController
    authorization of create/destroy micropost
      when non-login
        create
          behaves like error flash
            should eq "Please log in"
          behaves like redirect to path
            should redirect to "/login"
        destroy
          behaves like error flash
            should eq "Please log in"
          behaves like redirect to path
            should redirect to "/login"

Finished in 4.68 seconds (files took 2.33 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/)

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

### 以上