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

はじめに

今回は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

第11章

リスト11.18 アカウント有効化のプレビューメソッド(完成)

spec/mailers/previews/user_mailer_preview.rb

class UserMailerPreview < ActionMailer::Preview

  def account_activation
    user = User.first
    user.activation_token = User.new_token
    UserMailer.account_activation(user)
  end
end

リスト11.20 現在のメールの実装をテストする

spec/mailers/user_mailer_spec.rb

require "rails_helper"

RSpec.describe UserMailer, type: :mailer do
  describe "account_activation" do
    let(:user) { FactoryBot.create(:user) }
  #account_activationの引数を忘れないこと!!
    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
   # CGIモジュールのescapeメソッドを使用している。
   # @が%40に変換される
      expect(mail.body.encoded).to match CGI.escape(user.email)
    end
  end
end

Railsチュートリアルにあるuser.activation_token = User.new_tokenは必要ありません。
なぜなら、FactoryBot.createでテストユーザを作成しており、その際に Userモデルにあるbefore_create :create_activation_digestが実行されるからです。

リスト11.29 Userテスト内の抽象化したauthenticated?メソッド

spec/models/user_spec.rb

require 'rails_helper'

RSpec.describe User, type: :model do
 ・・・
  # authenticated?のテスト
  it 'returns false for a user with nil digest' do
    expect(user.authenticated?(:remember, '')).to be_falsy
  end
end

リスト11.29 ユーザー登録にテストにアカウント有効化を追加する

有効化用のメールを送信するまでのテストを
既存の「users_request_spec.rb」に追加します。

spec/requests/users_request_spec.rb

require 'rails_helper'

RSpec.describe "Users", type: :request do
・・・
  describe "POST /users" do
    let(:user) { FactoryBot.attributes_for(:user) }

    it "adds new user with correct signup information and sends an activation email" do
      aggregate_failures do
        expect do
          post users_path, params: { user: user }
        end.to change(User, :count).by(1)
 # ここから追加、及び修正しています。
        expect(ActionMailer::Base.deliveries.size).to eq(1)
        expect(response).to redirect_to root_url
        expect(is_logged_in?).to be_falsy
      end
    end
  end
end

次に「account_activations_request_spec.rb」で、アカウント有効化に対してのテストを行います。
また、assignsメソッドは使用せずに書いています。
まずは新しいテストユーザを追加します。

spec/factories/users.rb

FactoryBot.define do
  factory :user do
    name { "TestUser" }
    sequence(:email) { |n| "test#{n}@example.com" }
    password { "foobar" }
    password_confirmation { "foobar" }
    activated { true }
    activated_at { Time.zone.now }

    trait :admin do
      admin { true }
    end

# 新しく追加します。
    trait :no_activated do
      activated { false }
      activated_at { nil }
    end
  end
end

次に「account_activations_request_spec.rb」にテストを書いていきます。

spec/requests/account_activations_request_spec.rb

require 'rails_helper'

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

  # 正しいトークンと間違ったemailの場合
  context 'when user sends right token and wrong email' do
    before do
      get edit_account_activation_path(
        user.activation_token,
        email: 'wrong',
      )
    end

    it "falis login" do
      expect(is_logged_in?).to be_falsy
      expect(response).to redirect_to root_url
    end
  end

  # 間違ったトークンと正しいemailの場合
  context 'when user sends wrong token and right email' do
    before do
      get edit_account_activation_path(
        'wrong',
        email: user.email,
      )
    end

    it "falis login" do
      expect(is_logged_in?).to be_falsy
      expect(response).to redirect_to root_url
    end
  end

  # トークン、emailが両方正しい場合
  context 'when user sends right token and right email' do
    before do
      get edit_account_activation_path(
        user.activation_token,
        email: user.email,
      )
    end

    it "succeeds login" do
      expect(is_logged_in?).to be_truthy
      expect(response).to redirect_to user
    end
  end
end