根据迈克尔哈特尔关于Ruby on Rails的书,我似乎无法理解为什么其中一个测试通过而另一个测试失败,考虑到它们大致相同。这源于练习9.6-9中的UsersController#destroy
测试。
这是我的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 response.should be_success is never true的后续内容 “
修改
这是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
。
这是不同测试运行的输出:
spec/requests/user_pages_spec.rb
上的rspec试图删除user
:
..The current user is: Person 35
Is user admin?: true
..
Finished in 8.16 seconds
4 examples, 0 failures
spec/requests/user_pages_spec.rb
上的rspec试图删除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)>'
尝试删除spec/requests/authentication_pages_spec.rb
或user
的{{1}}上的rspec:
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)>'
一次?
将解决方案视为最后答案。
答案 0 :(得分:1)
在回答您的第一个问题时,user
和non-admin
之间的区别在于您在外user
块中以describe
身份登录。如果您后续尝试以admin
身份登录失败并且您仍然以user
身份登录,则可以解释您所看到的行为。您尚未提供valid_signin
的定义,但如果它不能独立于您是否已登录并已导航到登录页面,那么这将解释正在发生的事情。
同样,您的authentication_pages_spec.rb
示例完全依赖于valid_signin
成功运作。虽然您之前未在此示例中登录过,但您还没有进行任何导航,因此如果valid_signin
是一个简单的表单填充(因为它是在{{{}的3.2版本中定义的3}}),那就解释了为什么它在这种情况下失败了。
顺便说一句,如果你将3.2代码片段与4.0代码片段混合在一起,你会遇到很多问题。
其他人注意:这是http://ruby.railstutorial.org/book/ruby-on-rails-tutorial?version=3.2
的后续内容答案 1 :(得分:0)
好吧,显然我设法解决了这个问题。问题的根源是let(...)
懒惰评估,这意味着“直到第一次才评估它
调用它定义的方法。“。文档here。
相反,let!(..:)
可用于强制进行评估。我要感谢频道 #rubyonrails 中的 NemesisD 指出这一点,并在Stackoverflow上点击 Peter Alfvin 。
传递测试的最终代码如下(请参阅从let
更改为let!
):
要求'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{ valid_signin admin }
it "should be able to delete another user" do
expect { delete user_path(user) }.to change(User, :count).by(-1)
end
.
.
.
end