Activation test and refactoring
這節要撰寫帳號啟動的整合測試,之前已經為註冊寫過一個測試,我們會在那個測試加幾個步驟:
test/integration/users_signup_test.rb
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
def setup
ActionMailer::Base.deliveries.clear
end
test "invalid signup information" do
get signup_path
assert_no_difference 'User.count' do
post users_path, user: { name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar" }
end
assert_template 'users/new'
assert_select 'div#error_explanation'
assert_select 'div.field_with_errors'
end
test "valid signup information with account activation" do
get signup_path
assert_difference 'User.count', 1 do
post users_path, user: { name: "Example User",
email: "[email protected]",
password: "password",
password_confirmation: "password" }
end
assert_equal 1, ActionMailer::Base.deliveries.size
user = assigns(:user)
assert_not user.activated?
# Try to log in before activation.
log_in_as(user)
assert_not is_logged_in?
# Invalid activation token
get edit_account_activation_path("invalid token")
assert_not is_logged_in?
# Valid token, wrong email
get edit_account_activation_path(user.activation_token, email: 'wrong')
assert_not is_logged_in?
# Valid activation token
get edit_account_activation_path(user.activation_token, email: user.email)
assert user.reload.activated?
follow_redirect!
assert_template 'users/show'
assert is_logged_in?
end
end
其中這行:
assert_equal 1, ActionMailer::Base.deliveries.size
是確認只發送一封郵件,因為 deliveries 是全域陣列,會統計所有發出的郵件,所以要在 setup 重設它,以防其他測試發送了郵件(之後會提到)。我們也第一次使用了 assigns 方法,它的用途是在相對應的 action 中獲取實例變數。
例如,Users controller 中的 create action 定義了一個 @user 變數,所以可以在測試中使用 assigns(:user) 來取得這個變數。
注意,我們也把之前的註解拿掉了。
執行測試,應該會通過(Green):
$ bundle exec rake test
寫完測試後,就可以進行重構,我們要把某些 controller 裡處理使用者行為的程式碼移到 model 裡。我們會定義一個 activate 方法,來更新使用者的啟動狀態;還要定義一個 send_activation_email 方法,用來寄出啟動郵件。
以下是重構的程式碼:
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
private
.
.
.
end
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
@user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
.
.
.
end
app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.activate
log_in user
flash[:success] = "Account activated!"
redirect_to user
else
flash[:danger] = "Invalid activation link"
redirect_to root_url
end
end
end
注意,在 app/models/user.rb 中沒有使用 user. 因為 User model 沒有這個變數:
-user.update_attribute(:activated, true)
-user.update_attribute(:activated_at, Time.zone.now)
+update_attribute(:activated, true)
+update_attribute(:activated_at, Time.zone.now)
也可以把 user 改成 self,但之前提過不能在 model 裡加 self。同時,在調用 UserMailer 時,把 @user 改成 self:
-UserMailer.account_activation(@user).deliver_now
+UserMailer.account_activation(self).deliver_now
就算是簡單的重構,也可能忽略這樣的細節,所以好的測試能捕捉這些問題,現在執行測試應該會通過(Green):
$ bundle exec rake test
啟動帳號完成了,先來提交一下:
$ git add -A
$ git commit -m "Add account activations"