根据 Michael Hartl 关于 Ruby on Rails 的书,我似乎无法理解为什么其中一个测试通过而另一个失败,考虑到它们的作用基本相同。这源自UsersController#destroy
练习 9.6-9 中的测试。
这是我的spec/requests/user_pages_spec.rb
:
require 'spec_helper'
describe "User pages" do
subject { page }
describe "index" do
let(:user){ FactoryGirl.create(:user) }
#before/after ALL TESTS, not USERS
before(:all){ 30.times {FactoryGirl.create(:user) }}
after(:all) {User.delete_all}
#before EACH TEST, no user
before(:each) do
valid_signin user
visit users_path
end
describe "delete links" do
it { should_not have_link('delete') }
describe "as an admin user" do
let(:admin) { FactoryGirl.create(:admin) }
let(:non_admin) { FactoryGirl.create(:user) }
before do
valid_signin admin
visit users_path
end
it "should be able to delete another user" do
expect { delete user_path(user) }.to change(User, :count).by(-1)
end
.
.
.
end
这是我的spec/requests/authentication_pages_spec.rb
:
需要'spec_helper'
describe "AuthenticationPages" do
subject{ page }
describe "signin page" do
before { visit signin_path }
it { should have_selector('h1',text: 'Sign in') }
it { should have_selector('title', text: full_title('Sign in')) }
end
describe "signin" do
before { visit signin_path }
#INVALID INFO
describe "with invalid information" do
before { click_button "Sign in"}
it{ should have_selector('title', text: 'Sign in')}
#it{ should have_selector('div.alert.alert-error', text: 'Invalid')}
it{ should have_error_message('Invalid')}
describe "after visiting another page" do
before { click_link "Home" }
#it{ should_not have_selector('div.alert.alert-error')}
it{ should_not have_error_message()}
#exercise 9.6-3
it{ should_not have_link('Profile')}
it{ should_not have_link('Settings')}
end
end
#VALID INFO
describe "with valid information" do
let(:user) { FactoryGirl.create(:user) }
before{ valid_signin(user) }
it{ should have_selector('title', text: user.name)}
it{ should have_link('Users', href: users_path)}
it{ should have_link('Profile', href: user_path(user))}
it{should have_link('Settings', href: edit_user_path(user))}
it{ should have_link('Sign out', href: signout_path)}
it{ should_not have_selector('Sign in', href:signin_path)}
describe "followed by signout" do
before{click_link "Sign out"}
it{ should have_link('Sign in') }
end
#Exercise 9.6-6
describe "accessing new and create actions" do
describe "through website" do
before{visit signup_path}
it{ should_not have_selector('h1',text:"Sign up")}
it{ should_not have_button("Create my account")}
end
describe "through a POST request" do
before { post users_path}
specify { response.should redirect_to(root_path)}
end
end
end
end
describe "authorization" do
describe "as non-admin user" do
let(:admin) {FactoryGirl.create(:admin)}
let(:non_admin) {FactoryGirl.create(:user)}
before{valid_signin non_admin}
#Check that loggin to nonadmin works(debug ex.9.6-9)
describe "should render the non-admin profile page" do
it{ should have_selector('title', text: non_admin.name)}
end
describe "submitting a DELETE request to the Users#destroy action" do
before do
delete user_path(admin)
#puts response.message
#puts response.success?
end
specify{ response.should redirect_to(root_path) }
specify{ response.should_not be_success }
end
end
#Exercise 9.6-9 prevent admin from destroying himself
describe "as admin user" do
let(:user){FactoryGirl.create(:user)}
let(:admin){FactoryGirl.create(:admin)}
let(:non_admin){FactoryGirl.create(:user)}
before do
valid_signin admin
visit users_path
end
it "should be able to delete another user" do
expect { delete user_path(user) }.to change(User, :count).by(-1)
end
.
.
.
end
我从与应用程序的交互中知道删除用户确实有效,问题在于对其进行测试。这里感兴趣的测试是描述为“应该能够删除另一个用户”的测试,这在文件user_pages_spec.rb
和authentication_pages_spec.rb
.
有两件事我似乎无法理解:
在
user_pages_spec.rb
测试中expect { delete user_path(user) }.to change(User,:count).by(-1)
确实通过了,但是如果我将其更改为expect { delete user_path(non_admin) }.to change(User,:count).by(-1)
,它会失败。这是为什么?它们都是使用相同的工厂参数创建的。为什么考试中
authentication_pages_spec.rb
永远不会通过?不管是user_path(user)
还是user_path(non_admin)
。
这是我的工厂:
FactoryGirl.define do
factory :user do
sequence(:name){ |n| "Person #{n}" }
sequence(:email){ |n| "person_#{n}@example.com"}
password "foobar"
password_confirmation "foobar"
factory :admin do
admin true
end
end
end
这是我的users_controller.rb
:
class UsersController < ApplicationController
before_filter :signed_in_user, only: [:index, :edit, :update]
before_filter :correct_user, only: [:edit, :update]
before_filter :admin_user, only: [:destroy]
def new
#change for exercise 9.6-6
if signed_in?
redirect_to root_path
else
@user=User.new
end
end
def show
@user=User.find(params[:id])
end
def create
if signed_in?
redirect_to root_path
else
@user = User.new(params[:user])
if @user.save
sign_in @user
flash[:success]="Welcome to the Sample App!"
# Handle a successful save.
redirect_to @user
else
render 'new'
end
end
end
def edit
#@user= User.find(params[:id]) <----we can delete this because the before filter correct_user now defines @user variable
end
def update
#@user = User.find(params[:id])
if @user.update_attributes(params[:user])
# Handle a successful update.
flash[:success]="Profile updated"
sign_in @user
redirect_to @user
else
render 'edit'
end
end
def index
#@users= User.all
@users= User.paginate(page: params[:page])
end
def destroy
puts "The current user is:"+current_user.name
puts "Is user admin?:"+current_user.admin.to_s
User.find(params[:id]).destroy
flash[:success]="User destroyed."
redirect_to users_path
end
private
def signed_in_user
unless signed_in?
store_location
redirect_to signin_path, notice: "Please sign in."
end
end
def correct_user
@user =User.find(params[:id])
redirect_to(root_path) unless current_user?(@user)
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
和模型 user.rb
:
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# name :string(255)
# email :string(255)
# created_at :datetime not null
# updated_at :datetime not null
#
class User < ActiveRecord::Base
attr_accessible :email, :name, :password, :password_confirmation
has_secure_password
before_save { self.email.downcase! }
#callback for session token generation
before_save :create_remember_token
validates :name, presence: true, length: {maximum: 50}
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false}
#validates :password, presence: true, length:{minimum:6}
validates :password, length:{minimum:6}
validates :password_confirmation, presence: true
private
def create_remember_token
self.remember_token=SecureRandom.urlsafe_base64
end
end
这是定义的支持文件valid_signin
:
include ApplicationHelper
def valid_signin(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
# Sign in when not using Capybara as well.
cookies[:remember_token] = user.remember_token
end
def valid_signup(user)
fill_in "Name", with: user.name
fill_in "Email", with: user.email
fill_in "Password", with: user.password
fill_in "Confirm Password", with: user.password_confirmation
#click_button "Sign in"
end
RSpec::Matchers.define :have_error_message do |message|
match do |page|
page.should have_selector('div.alert.alert-error', text: message)
end
end
RSpec::Matchers.define :have_success_message do |message|
match do |page|
page.should have_selector('div.alert.alert-success', text: message)
end
end
“ 其他人注意:这是Rails 响应的后续版本。应该是_success 永远不会是真的 ”
编辑
这是SessionsHelper
(spec/helpers/sessions_helper.rb):
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token]=user.remember_token
current_user=user
end
def signed_in?
!current_user.nil?
end
def current_user=(user)
@current_user = user
end
def current_user
@current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
def current_user?(user)
user == current_user
end
def sign_out
current_user=nil
cookies.delete(:remember_token)
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
def store_location
session[:return_to] = request.fullpath
end
end
编辑 2添加puts
到UsersController#destroy
.
这是不同测试运行的输出:
rspecspec/requests/user_pages_spec.rb
尝试删除user
:
..The current user is: Person 35
Is user admin?: true
..
Finished in 8.16 seconds
4 examples, 0 failures
rspecspec/requests/user_pages_spec.rb
尝试删除non_admin
:
The current user is: Person 35
Is user admin?: true
F.
Failures:
1) User pages index delete links as an admin user should be able to delete another user
Failure/Error: expect { delete user_path(non_admin) }.to change(User, :count).by(-1)
count should have been changed by -1, but was changed by 0
# ./spec/requests/user_pages_spec.rb:48:in `block (5 levels) in <top (required)>'
rspecspec/requests/authentication_pages_spec.rb
尝试删除user
or non_admin
:
Run options: include {:full_description=>/(?-mix:as\ admin\ user)/}
The current user is: Person 1
Is user admin?: true
FThe current user is: Person 3
Is user admin?: true
FThe current user is: Person 5
Is user admin?: true
.
Failures:
1) AuthenticationPages authorization as admin user should be able to delete another user
Failure/Error: expect { delete user_path(user) }.to change(User, :count).by(-1)
count should have been changed by -1, but was changed by 0
# ./spec/requests/authentication_pages_spec.rb:99:in `block (4 levels) in <top (required)>'
我仍然不确定为什么它会执行 3 次,每次执行一次let(...)
?
最终编辑
将解决方案视为最后一个答案。