【Rspec】メール認証を使ったユーザー登録のテストについてアドバイスをお願いいたします
Q&A
Closed
解決したいこと
Ruby on Railsでオリジナルアプリを作成中です。
Railsチュートリアルに倣ってUserモデルを作成し、
Everyday Railsを拾い読みしながらRspecでテストを書いていますが
テストが通らず詰まっています。
具体的には、メール認証を使ったユーザー登録のテストをシステムスペックで書いていますが、
下記内容でテストが失敗しています。
Failures:
1) Users User CRUD creating a new user creates a new user
Failure/Error: visit edit_user_account_activation_path(@user.activation_token, email: @user.email)
ActionController::UrlGenerationError:
No route matches {:action=>"edit", :controller=>"user_account_activations", :email=>"test@example.com", :id=>nil}, possible unmatched constraints: [:id]
Did you mean? edit_user_account_activation_url
ブラウザで手動で動かしてみると期待通りの動きをしているので、
ルーティングや他のコードが間違っているわけではなさそうだと考えており、
テストの理解が不足していてその書き方が誤っているのだと思うのですが、
原因と修正すべき点や確認すべき事項などアドバイスいただけますと幸いです。
環境:ローカル環境
version | |
---|---|
macOS Big Sur | 11.2.3 |
Ruby | 3.0.0 |
Rails | 6.1.3 |
Rspec | 3.10 |
(rspec-rails) | 5.0.1 |
capybara | 3.26 |
該当するソースコード
require 'rails_helper'
RSpec.describe 'Users', js: true, type: :system do
describe 'User CRUD' do
let(:user) { FactoryBot.build(:user) }
let(:user_not_activated) { FactoryBot.build(:user, activated: false) }
let(:another_user) { FactoryBot.create(:user, email: 'another@example.com') }
before do
@number_of_users = User.count
ActionMailer::Base.deliveries.clear
end
context 'creating a new user' do
it 'creates a new user' do
visit signup_path
fill_in '名前', with: user_not_activated.name
fill_in 'メールアドレス', with: user_not_activated.email
fill_in 'パスワード', with: user_not_activated.password
fill_in 'パスワード(確認)', with: user_not_activated.password_confirmation
click_button 'ユーザー登録'
expect(ActionMailer::Base.deliveries.size).to eq 1
@user = User.find_by(email: user_not_activated.email)
expect(@user).to_not be_activated
visit edit_user_account_activation_path(@user.activation_token, email: @user.email)
@user.reload
expect(@user).to be_activated
expect(get_me_the_cookie('remember_token')).to_not eq nil
expect(page).to have_current_path edit_user_url(@user)
expect(page).to have_content 'ようこそ!'
expect(User.count).to eq @number_of_users + 1
end
#以下略
class User < ApplicationRecord
attr_accessor :remember_token, :activation_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true
VALID_PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)\w{8,12}\z/
has_secure_password
validates :password, presence: true,
format: { with: VALID_PASSWORD_REGEX, message: 'は半角8~12文字で英大文字・小文字・数字それぞれ1文字以上含む必要があります' },
allow_nil: true
# 渡された文字列のハッシュ値を返す
def self.digest(string)
cost = if ActiveModel::SecurePassword.min_cost
BCrypt::Engine::MIN_COST
else
BCrypt::Engine.cost
end
BCrypt::Password.create(string, cost: cost)
end
# ランダムなトークンを返す
def self.new_token
SecureRandom.urlsafe_base64
end
# 永続セッションのためにユーザーをデータベースに記憶する
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# 渡されたトークンがダイジェストと一致したらtrueを返す
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
def forget
update_attribute(:remember_digest, nil)
end
private
def downcase_email
email.downcase!
end
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
class UsersController < ApplicationController
before_action :logged_in_user, only: %i[show edit update destroy]
before_action :correct_user, only: %i[edit update destroy]
def new
@user = User.new
return unless logged_in?
redirect_to root_url
end
def create
@user = User.new(user_params)
if @user.save
UserMailer.account_activation(@user).deliver_now
flash[:info] = 'アカウント認証メールを確認して登録を完了してください'
redirect_to root_url
else
render 'new'
end
end
#以下略
class UserAccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.update_attribute(:activated, true)
user.update_attribute(:activated_at, Time.zone.now)
log_in user
flash[:success] = 'ようこそ!'
redirect_to edit_user_url user
else
flash[:danger] = 'このURLは無効です'
redirect_to root_url
end
end
end
自分で試したこと
pメソッドで@user
の中身を確認しました。
#これより前省略
context 'creating a new user' do
it 'creates a new user' do
visit signup_path
fill_in '名前', with: user_not_activated.name
fill_in 'メールアドレス', with: user_not_activated.email
fill_in 'パスワード', with: user_not_activated.password
fill_in 'パスワード(確認)', with: user_not_activated.password_confirmation
click_button 'ユーザー登録'
@user = User.find_by(email: user_not_activated.email)
expect(ActionMailer::Base.deliveries.size).to eq 1
expect(@user).to_not be_activated
p @user.activation_token # <---- デバッグ用に追記
p @user # <---- デバッグ用に追記
visit edit_user_account_activation_path(@user.activation_token, email: @user.email)
#これより後省略
その結果、(エラーメッセージにも出ていた通りですが)
@user.activation_token
の中身がnil
となっており、これが原因だと考えているのですが
なぜnil
になってしまうのかがわかりません。
(@user.activation_token
から生成される@user.activation_digest
には値が入っていました)
その他情報の過不足ありましたらご教示ください。
よろしくお願いいたします。