Rails Tutorialの第４版をRSpecでテスト-4

[Ruby on Rails チュートリアル][link-1]の第4版を学習し、[Everyday Rails - RSpecによるRailsテスト入門][link-2]でRSpecを学習したので、[Ruby on Rails チュートリアル][link-1]をRSpecでテストしてみました。
今回は第10章のテストを書きます。

第3章～第9章のテストは以下。
 - [Railsチュートリアルの第4版をRSpecでテスト-1][link-3]（第3章～第6章）
- [Railsチュートリアルの第4版をRSpecでテスト-2][link-4]（第7章～第8章）
 - [Railsチュートリアルの第4版をRSpecでテスト-3][link-5]（第9章）

[link-1]:https://railstutorial.jp/
[link-2]:https://leanpub.com/everydayrailsrspec-jp
[link-3]:https://qiita.com/yano12/items/eba8693272c31d6af7f0
[link-4]:https://qiita.com/yano12/items/4ef93129a50249adcb8d
[link-5]:https://qiita.com/yano12/items/319615e6e5fdcd7d844a
[link-6]:https://qiita.com/yano12/items/99e3fd4213f61339bad4
[link-7]:https://railstutorial.jp/chapters/account_activation?version=5.1#sec-exercises_mailer_templates
[link-8]:https://qiita.com/yano12/items/f5167416f3c20e1896d4
[link-9]:https://railstutorial.jp/chapters/password_reset?version=5.1#cha-password_reset
[link-10]:https://qiita.com/IrieLuka/items/34b0edccecc036d24748

# ユーザーの編集・更新のテスト
初めにユーザーコントローラのeditアクション、updateアクションをテストします。
リクエストスペックに以下のテストを追加します。

```spec/requests/user_spec.rb
require 'rails_helper'

RSpec.describe "User pages", type: :request do
  let(:user) { FactoryBot.create(:user) }
  let(:other_user) { FactoryBot.create(:user) }
  
  describe "GET #new" do
    # 中身は省略
  end
  
  describe "GET #show" do
    # 中身は省略
  end
  
  describe "#edit" do
    # 認可されたユーザーとして
    context "as an authorized user" do
      it "responds successfully" do
        sign_in_as user
        get edit_user_path(user)
        expect(response).to be_success
        expect(response).to have_http_status "200"
      end
    end
    
    # ログインしていないユーザーの場合
    context "as a guest" do 
      # ログイン画面にリダイレクトすること
      it "redirects to the login page" do
        get edit_user_path(user)
        expect(response).to have_http_status "302"
        expect(response).to redirect_to login_path
      end
    end
    
    # アカウントが違うユーザーの場合
    context "as other user" do 
      # ホーム画面にリダイレクトすること
      it "redirects to the login page" do
        sign_in_as other_user
        get edit_user_path(user)
        expect(response).to redirect_to root_path
      end
    end
  end
  
  describe "#update" do
    # 認可されたユーザーとして
    context "as an authorized user" do
      # ユーザーを更新できること
      it "updates a user" do
        user_params = FactoryBot.attributes_for(:user, name: "NewName")
        sign_in_as user
        patch user_path(user), params: { id: user.id, user: user_params }
        expect(user.reload.name).to eq "NewName"
      end
    end

    # ログインしていないユーザーの場合
    context "as a guest" do 
      # ログイン画面にリダイレクトすること
      it "redirects to the login page" do
        user_params = FactoryBot.attributes_for(:user, name: "NewName")
        patch user_path(user), params: { id: user.id, user: user_params }
        expect(response).to have_http_status "302"
        expect(response).to redirect_to login_path
      end
    end
    
    # アカウントが違うユーザーの場合
    context "as other user" do 
      # ユーザーを更新できないこと
      it "does not update the user" do
        user_params = FactoryBot.attributes_for(:user, name: "NewName")
        sign_in_as other_user
        patch user_path(user), params: { id: user.id, user: user_params }
        expect(user.reload.name).to eq other_user.name
      end
      
      # ホーム画面にリダイレクトすること
      it "redirects to the login page" do
        user_params = FactoryBot.attributes_for(:user, name: "NewName")
        sign_in_as other_user
        patch user_path(user), params: { id: user.id, user: user_params }
        expect(response).to redirect_to root_path
      end
    end
  end
end
```

editアクションは基本的にshowアクションと同じですが、updateアクションはPATCHリクエストを使っています。
テストを実行しパスするのを確認します。

続いて、編集の失敗・成功のテストをフィーチャースペックに書いていきます。
ただし、パスワードの更新が上手くいかなかったのでとりあえずコメントアウトしてあります。

```spec/features/users_edit_spec.rb
require 'rails_helper'

RSpec.feature "Edit", type: :feature do
  let(:user) { FactoryBot.create(:user) }
  
  # ユーザーは編集に成功する
  scenario "successful edit" do
    valid_login(user)    # spec/support/login_support.rbに定義済み
    visit user_path(user)
    click_link "プロフィール編集"
    
    fill_in "メールアドレス", with: "edit@example.com"
    # fill_in "パスワード", with: user.password, match: :first
    # fill_in "新しいパスワード", with: "test123", match: :first
    # fill_in "新しいパスワード確認", with: "test123"
    click_button "変更する"
      
    expect(current_path).to eq user_path(user)
    expect(user.reload.email).to eq "edit@example.com"
    # expect(user.reload.password).to eq "test123"
  end
  
  # ユーザーは編集に失敗する
  scenario "unsuccessful edit" do
    valid_login(user)    # spec/support/login_support.rbに定義済み
    visit user_path(user)
    click_link "プロフィール編集"
    
    fill_in "メールアドレス", with: "foo@invalid"
    fill_in "新しいパスワード", with: "foo", match: :first
    fill_in "新しいパスワード確認", with: "bar"
    click_button "変更する"
    
    expect(user.reload.email).to_not eq "foo@invalid"
  end
end
```

実行しテストがパスするのを確認します。

# フレンドリーフォーワーディングのテスト
フレンドリーフォーワーディングのテストでは、ログインしていないユーザーが編集ページにアクセスしようとした際、ユーザーがログインした後にはその編集ページにリダイレクトされることをテストします。
このテストは、先程作成したユーザー編集のフィーチャースペックを少し変えるだけです。

```spec/features/users_edit_spec.rb
require 'rails_helper'

RSpec.feature "Edit", type: :feature do
  let(:user) { FactoryBot.create(:user) }
  
  # ユーザーは編集に成功する
  scenario "successful edit" do
    visit user_path(user)   # 順序を入れ替えただけ
    valid_login(user)       # 順序を入れ替えただけ
    # ・・・以下省略

```

# ユーザーの削除のテスト
リクエストスペックにユーザー削除のテストを追加します。

```spec/requests/user_spec.rb
require 'rails_helper'

RSpec.describe "User pages", type: :request do
  let(:user) { FactoryBot.create(:user) }
  let(:other_user) { FactoryBot.create(:user) }

  # 他のアクションのテストは省略

  describe "#destroy" do
    # 認可されたユーザーとして
    context "as an authorized user" do
      # ユーザーを削除できること
      it "deletes a user" do
        sign_in_as user
        expect {
          delete user_path(user), params: { id: user.id }
        }.to change(User, :count).by(-1)
      end
    end

    # アカウントの違うユーザーの場合
    context "as au unauthorized user" do
      # ホーム画面にリダイレクトすること
      it "redirects to the dashboard" do
        sign_in_as other_user
        delete user_path(user), params: { id: user.id }
        expect(response).to redirect_to root_path
      end
    end

    # ゲストとして
    context "as a guest" do
      it "returns a 302 response" do
        delete user_path(user), params: { id: user.id }
        expect(response).to have_http_status "302"
      end

      it "redirects to the sign-in page" do
        delete user_path(user), params: { id: user.id }
        expect(response).to redirect_to login_path
      end
    end
  end
end
```

実行しテストがパスするのを確認します。

今回は以上です。
11章以降は完成し次第書いていきます。

#追記
第11章以降のテストを書きました。
 - [Railsチュートリアルの第4版をRSpecでテスト-5][link-8]（第11章）
 - [Railsチュートリアルの第4版をRSpecでテスト-6][link-10]（第12章）