RSpec测试破坏方法(Rails Tutorial 3.2 Ch.9,Ex.10)

注意:我已经阅读了这个问题和答案,但由于某种原因,代码对我不起作用。 (见下面我得到的错误)

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测试的解决方案, 如果没有删除链接 ,我将在这里复制:

来自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 ' # ./spec/requests/user_pages_spec.rb:180:in `block (3 levels) in ' 

所以,我的问题是:1)为什么上面的代码失败了? 2)如何模拟“删除”以便在我的控制器中调用destroy动作?

环境:Mac OSX ruby​​ 1.9.3p194 Rails 3.2.3

用于测试的gem:
group:test do gem’rspec-rails’,’2.9.0’gem’capybara’,’1.1.2’gem’rb-fsevent’,’0.4.3.1′,: require => false gem’growl’,’ 1.0.3’gem’守卫’spork’,’0.3.2’gem’spork’,’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), 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 ' # ./spec/requests/user_pages_spec.rb:190:in `block (3 levels) in ' 

我在FOREVER之后想出来了。

我不得不使用Rack :: Test来发出DELETE请求,但是Capybara和Rack :: Test没有共享相同的MockSession,所以我不得不拉入:remember_token和:!sample_app_session cookie并将它们放入DELETE请求中手动。 这是有效的。 (我在下面列出的另一个问题是,我有一个force_ssl语句,它不会让我的destroy操作被调用。

 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) (以及getpost等) – 是一个模拟ActionController :: TestCase请求的方法,所以它在你的测试中不可用。

因此,您唯一的选择是模拟浏览器中的单击。 我不知道你是如何隐藏你的删除链接,如果html存在但隐藏你应该能够点击它。 如果不存在(在生成视图时在服务器端删除),您可以使用page.execute_scriptpage.execute_script (但是您必须为此示例启用javascript :js => true )。 您可以添加链接:

 page.execute_script("$('body').append("Destroy")") 

或拨打ajax电话:

 page.execute_script("$.ajax({type:'DELETE',url:'https://stackoverflow.com/users/1'})") 

没有测试,但这样的事情应该工作。

我使用以下方法解决了同样的问题:

 describe "should not be able to delete themselves" do it { expect { delete user_path(admin) }.not_to change(User, :count) } end 

CallumD的解决方案对我有用,并且似乎与Michael Hartl教程其余部分推荐的技术最为一致。 但我想稍微收紧一下语法,使其与同一教程中的其他规范更加一致:

 it "should not be able to delete itself" do expect { delete user_path(admin) }.not_to change(User, :count) end 

这就是我最终得到的结果(Rspec 3.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规定的,所以恕我直言,它应该可以依赖它。

https://github.com/rails/rails/blob/5142d5411481c893f817c1431b0869be3745060f/activerecord/lib/active_record/persistence.rb#L91

试试这个:

 expect { delete :destroy, :id => admin }.to_not change(User, :count)