RailsチュートリアルのテストをRspecで書いてみた[第12章]

はじめに

今回はRspecの学習の一環として、RailsチュートリアルのテストをRspecで書いていきます。
至らない点があるかもしれませんが、その際はコメントにてご指摘をお願いします。

各種バージョン

Ruby 2.7.0
Rails 6.0.3.3
Rspec 3.9
Capybara 3.33.0
Factory_bot_rails 6.1.0

第12章

リスト12.12: パスワード再設定用メイラーメソッドのテストを追加する

spec/mailers/user_mailer_spec.rb

require "rails_helper"

RSpec.describe UserMailer, type: :mailer do
  let(:user) { FactoryBot.create(:user) }

  describe "account_activation" do
    let(:mail) { UserMailer.account_activation(user) }

    it "renders the headers" do
      expect(mail.subject).to eq "Account activation"
      expect(mail.to).to eq([user.email])
      expect(mail.from).to eq(["noreply@example.com"])
    end

    it "renders the body" do
      expect(mail.body.encoded).to match user.name
      expect(mail.body.encoded).to match user.activation_token
      expect(mail.body.encoded).to match CGI.escape(user.email)
    end
  end

  # ここからが今回のテストです。
  describe "password_reset" do
  # 下の一文を忘れないこと!!
    before { user.reset_token = User.new_token }

    let(:mail) { UserMailer.password_reset(user) }

    it "renders the headers" do
      expect(mail.subject).to eq("Password reset")
      expect(mail.to).to eq([user.email])
      expect(mail.from).to eq(["noreply@example.com"])
    end

    it "renders the body" do
      expect(mail.body.encoded).to match user.reset_token
      expect(mail.body.encoded).to match CGI.escape(user.email)
    end
  end
end

今回はrailsチュートリアルにあるようにuser.reset_token = User.new_token を行っています。
activation_tokenと違い、before_createで生成されていないからです。

リスト12.18: パスワード再設定の統合テスト

注意!!

Railsチュートリアルではassignsメソッドを使用してありますが、現在は非推奨になっています。
その為、assignsメソッドなしで書こうと思いましたが、結局Userモデル内にあるメソッドをそのまま利用しております。
(テストとしては駄目な気しかしないけど、他に思いつかない...)

models/user.rb

  # パスワード再設定の属性を設定する
  def create_reset_digest
    self.reset_token = User.new_token
    update(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now)
  end

以下が今回書いたテストです。

spec/requests/password_resets_request_spec.rb

require 'rails_helper'

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

# edit,updateアクションへのテストで必要です。
  before { user.create_reset_digest }

  describe "def new" do
    it "returns http success" do
      get "/password_resets/new"
      aggregate_failures do
        expect(response).to have_http_status(:success)
        expect(response.body).to include 'Forgot password'
      end
    end
  end

  describe "def create" do
    # メールアドレスが無効
    it 'falis create with invalid email' do
      post password_resets_path, params: { password_reset: { email: "" } }
      aggregate_failures do
        expect(response).to have_http_status(200)
        expect(response.body).to include 'Forgot password'
      end
    end

    # メールアドレスが有効
    it 'succeds create with valid email' do
      post password_resets_path, params: { password_reset: { email: user.email } }
      aggregate_failures do
        expect(user.reset_digest).not_to eq user.reload.reset_digest
        expect(ActionMailer::Base.deliveries.size).to eq 1
        expect(response).to redirect_to root_url
      end
    end
  end

  describe "def edit" do
    # メールアドレスが無効
    context 'when user sends correct token and wrong email' do
      before { get edit_password_reset_path(user.reset_token, email: "") }

      it 'fails' do
        expect(response).to redirect_to root_url
      end
    end

    # 無効なユーザ
    context 'when not activated user sends correct token and email' do
      before do
       # toggle!は真偽値を反対にして、破壊的メソッドなので保存します。
        user.toggle!(:activated)
        get edit_password_reset_path(user.reset_token, email: user.email)
      end

      it 'fails' do
        expect(response).to redirect_to root_url
      end
    end

    # メールアドレスが有効で、トークンが無効
    context 'when user sends wrong token and correct email' do
      before { get edit_password_reset_path("wrong", email: user.email) }

      it 'fails' do
        expect(response).to redirect_to root_url
      end
    end

    # メールアドレスもトークンも有効
    context 'when user sends correct token and email' do
      before { get edit_password_reset_path(user.reset_token, email: user.email) }

      it 'succeeds' do
        aggregate_failures do
          expect(response).to have_http_status(200)
          expect(response.body).to include "Reset password"
        end
      end
    end
  end

  describe "def update" do
    # 無効なパスワードとパスワード確認
    context "when user sends wrong password" do
      before do
        patch password_reset_path(user.reset_token),
              params: {
                email: user.email,
                user: {
                  password: "foobaz",
                  password_confirmation: "barquux",
                },
              }
      end

      it 'fails' do
        aggregate_failures do
          expect(response).to have_http_status(200)
          expect(response.body).to include "Reset password"
        end
      end
    end

    # パスワードが空
    context "when user sends blank password" do
      before do
        patch password_reset_path(user.reset_token),
              params: {
                email: user.email,
                user: {
                  password: "",
                  password_confirmation: "",
                },
              }
      end

      it 'fails' do
        aggregate_failures do
          expect(response).to have_http_status(200)
          expect(response.body).to include "Reset password"
        end
      end
    end

    # 有効なパスワードとパスワード確認
    context "when user sends correct password" do
      before do
        patch password_reset_path(user.reset_token),
              params: {
                email: user.email,
                user: {
                  password: "foobaz",
                  password_confirmation: "foobaz",
                },
              }
      end

      it 'fails' do
        aggregate_failures do
          expect(is_logged_in?).to be_truthy
          expect(response).to redirect_to user
        end
      end
    end
  end
end

リスト12.21: パスワード再設定の期限切れのテスト

spec/requests/password_resets_request_spec.rb

require 'rails_helper'

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

# 今回のテストでも必要です。
  before { user.create_reset_digest }
・・・
  describe "def check_expiration" do
    context "when user updates after 3 hours" do
      before do
        user.update_attribute(:reset_sent_at, 3.hours.ago)
        patch password_reset_path(user.reset_token),
              params: {
                email: user.email,
                user: {
                  password: "foobar",
                  password_confirmation: "foobar",
                },
              }
      end

      it "fails" do
        expect(response).to redirect_to new_password_reset_url
      end
    end
  end
end

リスト12.22: パスワード再設定が成功したらダイジェストをnilにする

spec/requests/password_resets_request_spec.rb

require 'rails_helper'

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

  before { user.create_reset_digest }
・・・
  describe "def update" do
 ・・・ 
    # 有効なパスワードとパスワード確認
    context "when user sends correct password" do
      before do
        patch password_reset_path(user.reset_token),
              params: {
                email: user.email,
                user: {
                  password: "foobaz",
                  password_confirmation: "foobaz",
                },
              }
      end

      it 'fails' do
        aggregate_failures do
          expect(is_logged_in?).to be_truthy
     # 下の一文を追加する。
          expect(user.reload.reset_digest).to eq nil
          expect(response).to redirect_to user
        end
      end
    end
end