0

Ruby on Rails に関する Michael Hartl の本に従って、テストの 1 つが成功し、別のテストが失敗する理由を理解できないようです。これは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

私が理解できないように見える2つのことがあります:

  1. 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)と失敗します。何故ですか?どちらも同じファクトリ パラメータで作成されます。

  2. なぜテストは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 の応答に続くものです。be_success が 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

EDIT 2putsに追加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を削除しようとしたときの rspec :usernon_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回、それぞれに1回行われるのか、まだわかりませんかlet(...)

最終編集

最後の回答として解決策を参照してください。

4

2 に答える 2

1

user最初の質問への回答として、との違いは、外側のブロックでnon-adminログインしたことです。その後 としてログインしようとして失敗し、 としてログインしたままになっている場合は、表示されている動作を説明できます。の定義を提供していませんが、既にサインインしていて、サインイン ページに移動したかどうかに関係なく機能しない場合は、何が起こっているかを説明できます。userdescribeadminuservalid_signin

同様に、あなたの例は完全に正常に動作authentication_pages_spec.rbすることに依存しています。valid_signinこの例では以前にログインしていませんが、ナビゲーションも行っていないため、ifvalid_signinは単純なフォーム入力です ( http://ruby.railstutorialのチュートリアルの 3.2 バージョンで定義されているとおり)。 .org/book/ruby-on-rails-tutorial?version=3.2 )、この場合に失敗する理由を説明します。

余談ですが、3.2 のコード スニペットと 4.0 のコード スニペットを混在させると、多くの問題が発生します。

他の人への注意: これはRails の応答に続くものです。be_success が true になることはありません

于 2013-09-25T12:17:14.833 に答える
0

さて、どうやら私は問題を解決することができました。問題の根本let(...)遅延評価されることでした。これは、「定義されたメソッドが最初に呼び出されるまで評価されない」ことを意味します。ドキュメンテーションはこちら

代わりに、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
于 2013-09-25T19:47:27.817 に答える