用于测试DELETE请求的Rspec类似代码给出了不同的结果

时间:2013-09-25 11:22:31

标签: ruby-on-rails ruby ruby-on-rails-3 rspec

根据迈克尔哈特尔关于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.rbauthentication_pages_spec.rb都相同。

我无法理解两件事:

  1. user_pages_spec.rbexpect { delete user_path(user) }.to change(User,:count).by(-1) 的测试会通过,但如果我将其更改为expect { delete user_path(non_admin) }.to change(User,:count).by(-1),则会失败。这是为什么?它们都使用相同的工厂参数创建。

  2. 为什么authentication_pages_spec.rb的测试从未通过?无论是user_path(user)还是user_path(non_admin)

  3. 这是我的工厂:

    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.rbuser的{​​{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)>' 一次?

    最终编辑

    将解决方案视为最后答案。

2 个答案:

答案 0 :(得分:1)

在回答您的第一个问题时,usernon-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