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方法.我试过Rspec test for destroy if no delete link的解决方案,我在这里复制:
来自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,: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
ruby1.9.3p194
Rails 3.2.3
宝石测试:
组:测试
宝石’rspec-rails’,’2.9.0′
宝石’capybara’,’1.1.2′
gem’rb-fsevent’,’0.4.3.1′,require =>假
宝石’咆哮’,’1.0.3′
宝石’护卫’,’0.3.2′
宝石’,’0.9.0′
gem’factory_girl_rails’,’1.4.0′
结束
更多信息
我已经尝试了很多方法来尝试模拟点击删除链接,而且似乎没有效果.我一直在使用调试器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),: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,: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请求手动.这是有效的(下面列出的另一个问题是,我有一个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),#{Capybara.current_session.driver.response.headers["Set-Cookie"]}" }. to_not change(User,:count) end end
我在我的users_controller.rb中的before_filters之后有一个force_ssl语句,这是以某种方式抛出的东西,所以我没有得到摧毁动作.
class UsersController < ApplicationController before_filter :signed_in_user,only: [:edit,:update,:index] before_filter :existing_user,only: [:new,:create] before_filter :correct_user,: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/
解决方法
所以你唯一的选择是在浏览器中模拟一个点击.我不知道你如何隐藏你的删除链接,如果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'})")
没有测试,但这样的东西应该工作.