注: this の質問と回答を読みましたが、何らかの理由でコードが機能していません。 (私が得ているエラーについては、以下を参照してください)
Railsチュートリアルの第9章の演習10では、次のように求められます。[ユーザーの]破棄アクションを変更して、管理ユーザーが自分自身を破棄しないようにします(最初にテストを記述します)。
ここで注意が必要なのはテストです。アプリケーションは現在のユーザーの「削除」リンクをすでに非表示にしているため、httpリクエストを直接実行する必要があります。
コードを機能させ、現在のユーザーの削除リンクを非表示にするコードを削除してテストしました。案の定、現在ログインしているユーザーの削除リンクをクリックすると、リダイレクトされて通知メッセージが表示されます。
users_controller.rbから
def destroy
@user = User.find(params[:id])
if current_user?(@user)
redirect_to users_path, notice: "You can't destroy yourself."
else
@user.destroy
flash[:success] = "User destroyed."
redirect_to users_path
end
end
私が抱えている問題は、削除要求を送信してdestroyメソッドを呼び出すこのテストを作成することです。私は 削除リンクがない場合のdestroyのRspecテスト からの解決策を試しました。これをここにコピーしています:
user_pages_spec.rbから
describe "destroy" do
let(:admin) { FactoryGirl.create(:admin) }
it "should not allow the admin to delete herself" do
sign_in admin
#expect { delete user_path(admin), method: :delete }.should change(User, :count)
expect { delete :destroy, :id => admin.id }.should_not change(User, :count)
end
end
しかし、これを実行すると、RSpecからこのエラーが発生します
Failures:
1) User Pages destroy should not allow the admin to delete herself
Failure/Error: expect { delete :destroy, :id => admin.id }.should_not change(User, :count)
ArgumentError:
bad argument (expected URI object or URI string)
# ./spec/requests/user_pages_spec.rb:180:in `block (4 levels) in <top (required)>'
# ./spec/requests/user_pages_spec.rb:180:in `block (3 levels) in <top (required)>'
だから、私の質問は次のとおりです:1)上記のコードが失敗するのはなぜですか? 2)コントローラーで破棄アクションを呼び出すために「削除」をシミュレートするにはどうすればよいですか?
環境:Mac OSX Ruby 1.9.3p194 Rails 3.2.3
テスト用の宝石:
[。 'growl'、 '1.0.3' gem'guard-spork '、' 0.3.2 'gem'spork'、 '0.9.0' gem'factory_girl_Rails '、' 1.4.0 'end
詳細クリックをシミュレートする方法をトン試してみました削除リンクにあり、どれも機能していないようです。私はデバッガーgemを使用して、destroyメソッドが呼び出されているかどうかを確認しています。リンクをクリックして別のユーザーを削除するテストでは、destroyメソッドが呼び出され、正常に機能します。
it "should be able to delete another user" do
expect { click_link('delete') }.to change(User, :count).by(-1)
end
しかし、削除要求を直接生成しようとしても、destroyメソッドを呼び出すことはできませんでした。
ご協力いただきありがとうございます!
意志
**更新**
私はDVGの提案を試しました:
describe "destroy" do
let(:admin) { FactoryGirl.create(:admin) }
it "should not allow the admin to delete herself" do
sign_in admin
#expect { delete user_path(admin), method: :delete }.should change(User, :count)
expect { delete :destroy, :id => admin }.to_not change(User, :count)
end
end
そして、このエラーが発生しました:
6) User Pages destroy should not allow the admin to delete herself
Failure/Error: expect { delete :destroy, :id => admin }.to_not change(User, :count)
ArgumentError:
bad argument (expected URI object or URI string)
# ./spec/requests/user_pages_spec.rb:190:in `block (4 levels) in <top (required)>'
# ./spec/requests/user_pages_spec.rb:190:in `block (3 levels) in <top (required)>'
[〜#〜]ソリューション[〜#〜]
私はFOREVERの後でそれを理解しました。
Rack :: Testを使用してDELETEリクエストを発行する必要がありましたが、CapybaraとRack :: Testは同じMockSessionを共有していないため、:remember_tokenと:!sample_app_sessionのCookieを取得してDELETEリクエストに入れる必要がありました。手動で。これがうまくいったものです。 (以下にリストされている、私が抱えていた他の問題は、destroyアクションが呼び出されないforce_sslステートメントがあったことでした。
describe "destroy" do
let!(:admin) { FactoryGirl.create(:admin) }
before do
sign_in admin
end
it "should delete a normal user" do
user = FactoryGirl.create(:user)
expect { delete user_path(user), {},
'HTTP_COOKIE' => "remember_token=#{admin.remember_token},
#{Capybara.current_session.driver.response.headers["Set-Cookie"]}" }.
to change(User, :count).by(-1)
end
it "should not allow the admin to delete herself" do
expect { delete user_path(admin), {},
'HTTP_COOKIE' => "remember_token=#{admin.remember_token},
#{Capybara.current_session.driver.response.headers["Set-Cookie"]}" }.
to_not change(User, :count)
end
end
Users_controller.rbのbefore_filtersの後にforce_sslステートメントがありましたが、これはどういうわけか物事を捨てていたので、destroyアクションにたどり着きませんでした。
class UsersController < ApplicationController
before_filter :signed_in_user, only: [:edit, :update, :index]
before_filter :existing_user, only: [:new, :create]
before_filter :correct_user, only: [:edit, :update]
before_filter :admin_user, only: :destroy
#force_ssl
def index
@users = User.paginate(page: params[:page])
end
def show
@user = User.find(params[:id])
@microposts = @user.microposts.paginate(page: params[:page])
end
def destroy
@user = User.find(params[:id])
if current_user?(@user)
redirect_to users_path, notice: "You can't destroy yourself."
else
@user.destroy
flash[:success] = "User destroyed."
redirect_to users_path
end
end
これらは解決策を見つけるのに役立ちました
https://Gist.github.com/484787
http://collectiveidea.com/blog/archives/2012/01/05/capybara-cucumber-and-how-the-cookie-crumbles/
あなたはrspecを混乱させています-統合テストであり、シミュレートされたブラウザーで実行されるRailsリクエストの仕様と、コントローラーを分離してテストするコントローラーの仕様。 delete(action, *args)
(およびget
、post
など)-ActionController :: TestCaseからのリクエストをシミュレートするメソッドであるため、テストでは使用できません。
したがって、唯一のオプションは、ブラウザでのクリックをシミュレートすることです。削除リンクを非表示にする方法がわかりません。HTMLはあるが非表示になっている場合は、クリックできるはずです。そこにない場合(ビューの生成時にサーバー側で削除)、capybaraのpage.execute_script
を使用できます(ただし、この例ではjavascriptを有効にする必要があります:js => true
)。リンクを追加し直すこともできます。
page.execute_script("$('body').append("<a href="/users/1" data-method="delete" rel="nofollow">Destroy</a>")")
またはajax呼び出しを行います:
page.execute_script("$.ajax({type:'DELETE',url:'/users/1'})")
テストしませんでしたが、このようなものは動作するはずです。
CallumDのソリューションは私にとってはうまくいき、MichaelHartlのチュートリアルの残りの部分で推奨されている手法と最も一致しているように見えました。しかし、同じチュートリアルの他の仕様との一貫性を高めるために、構文を少し厳しくしたいと思いました。
it "should not be able to delete itself" do
expect { delete user_path(admin) }.not_to change(User, :count)
end
私は以下を使用してこれと同じ問題を解決しました:
describe "should not be able to delete themselves" do
it { expect { delete user_path(admin) }.not_to change(User, :count) }
end
これは私が最終的に得たものです(Rspec3.2):
describe 'DELETE destroy' do
before :each do
delete :destroy, { id: current_partner_role }
end
it 'destroys role' do
expect(assigns(:role).destroyed?).to be true
end
"破壊されました?"メソッド自体はRailsで指定されているので、私見ではそれを信頼しても大丈夫です。
これを試して:
expect { delete :destroy, :id => admin }.to_not change(User, :count)