问题描述
遵循 Michael Hartl 关于 Ruby on Rails 的书,我似乎无法理解为什么其中一个测试通过而另一个失败,因为它们的效果大致相同.这源自测试练习 9.6-9 中的 UsersController#destroy
.
Following Michael Hartl's book on Ruby on Rails, I can't seem to understand why one of the tests pass and another fails, considering they do mostly the same. This derives from testing UsersController#destroy
from Exercise 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'
require '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",这在 user_pages_spec.rb
和 authentication_pages_spec.rb
两个文件中是相同的.
I know from interacting with the application that deleting users does work, the problem is with testing it.The test of interest here is the one described as "should be able to delete another user", which is the same in both files user_pages_spec.rb
and authentication_pages_spec.rb
.
有两件事我似乎无法理解:
There's two things I cannot seem to understand:
在
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)
,它会失败.这是为什么?它们都是使用相同的工厂参数创建的.
At
user_pages_spec.rb
the test withexpect { delete user_path(user) }.to change(User,:count).by(-1)
does pass, however if I change it toexpect { delete user_path(non_admin) }.to change(User,:count).by(-1)
, it fails. Why is that? They are both created with the same factory parameters.
为什么authentication_pages_spec.rb
中的测试从未通过?不管是user_path(user)
还是user_path(non_admin)
.
Why is the test in the authentication_pages_spec.rb
never passing? No matter whether it's user_path(user)
or 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
的支持文件:
This is the support file where valid_signin
is defined:
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 的后续永远不会是真的"
"Note to others: This is a follow-on to Rails response.should be_success is never true"
编辑
这是SessionsHelper
(spec/helpers/sessions_helper.rb):
This is the 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
EDIT 2 将 puts
添加到 UsersController#destroy
.
这是不同测试运行的输出:
This is the output for the different test running:
rspec on spec/requests/user_pages_spec.rb
试图删除 user
:
rspec on spec/requests/user_pages_spec.rb
trying to delete user
:
..The current user is: Person 35
Is user admin?: true
..
Finished in 8.16 seconds
4 examples, 0 failures
rspec on spec/requests/user_pages_spec.rb
试图删除 non_admin
:
rspec on spec/requests/user_pages_spec.rb
trying to delete 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)>'
rspec on spec/requests/authentication_pages_spec.rb
试图删除 user
或 non_admin
:
rspec on spec/requests/authentication_pages_spec.rb
trying to delete either 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(...)
一次?
I'm still not sure why it does it three times, one for each let(...)
?
将解决方案视为最后一个答案.
See solution as last answer.
推荐答案
针对你的第一个问题,user
和 non-admin
的区别在于你登录了在外部 describe
块中作为 user
.如果您随后尝试以 admin
身份登录失败,而您仍以 user
身份登录,这将解释您所看到的行为.您还没有提供 valid_signin
的定义,但如果它不工作独立于您是否已经登录并导航到登录页面,那么这将解释发生了什么.
In response to your first question, the difference between user
and non-admin
is that you logged in as user
in the outer describe
block. If your subsequent attempt to log-in as admin
is failing and you remain logged in as user
, that would explain the behavior you're seeing. You haven't provided the definition of valid_signin
, but if it doesn't work independent of whether you are already signed in and have navigated to the sign in page, then this would explain what's going on.
同样,您的 authentication_pages_spec.rb
示例完全依赖于 valid_signin
的成功运行.虽然您之前没有在本示例中登录,但您也没有进行任何导航,因此如果 valid_signin
是一个简单的表单填写(如本教程 3.2 版本中定义的那样)在 http://ruby.railstutorial.org/book/ruby-on-rails-tutorial?version=3.2),那么这就解释了为什么在这种情况下它失败了.
Similarly, your authentication_pages_spec.rb
examples rely entirely on valid_signin
working successfully. While you haven't logged in previously in this example, you haven't done any navigation either, so if valid_signin
is a simple form fill-in (as it was defined in the 3.2 version of the tutorial at http://ruby.railstutorial.org/book/ruby-on-rails-tutorial?version=3.2), then that would explain why it's failing in this case.
顺便说一句,如果您将 3.2 代码片段与 4.0 代码片段混合使用,您会遇到很多问题.
As an aside, if you're mixing 3.2 code snippets with 4.0 code snippets, you're going to encounter a lot of problems.
其他人注意:这是 Rails 响应的后续.should be_success 永远不是真的
这篇关于用于测试 DELETE 请求的 Rspec 类似代码给出了不同的结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!