【问题标题】:RSpec test destroy method (Rails Tutorial 3.2 Ch. 9, Ex. 10)RSpec 测试破坏方法(Rails Tutorial 3.2 Ch. 9, Ex. 10)
【发布时间】:2012-06-12 01:13:29
【问题描述】:

注意:我已阅读 this 问题和答案,但由于某种原因,该代码不适用于我。 (有关我遇到的错误,请参见下文)

Rails 教程第 9 章的练习 10 要求您: 修改 [for users] 的 destroy 操作以防止管理员用户销毁自己。 (先写一个测试。)

这里棘手的部分是测试它,因为应用程序已经为当前用户隐藏了“删除”链接,所以你必须直接进行 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

我遇到的问题是为此编写将发送删除请求并调用销毁方法的测试。我尝试了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, :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 红宝石 1.9.3p194 导轨 3.2.3

用于测试的宝石:
组:测试做 宝石'rspec-rails','2.9.0' 宝石'水豚','1.1.2' gem 'rb-fsevent', '0.4.3.1', :require => false 宝石“咆哮”,“1.0.3” 宝石 'guard-spork', '0.3.2' 宝石'spork','0.9.0' 宝石'factory_girl_rails','1.4.0' 结束

更多信息 我已经尝试了 ton 多种方法来尝试模拟单击删除链接,但似乎都不起作用。我一直在使用调试器 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 并将它们放入手动删除请求。这是有效的。 (下面列出的另一个问题是我有一个 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 语句,这不知何故把事情扔掉了,所以我从来没有去破坏行动。

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/

【问题讨论】:

  • 我刚刚发现的一件事,这不是我的问题,但可能是 a 问题是 let 方法是惰性的,所以我可以想象它搞砸了期望 to_not改变功能。所以我调整了代码以使用 let!创建管理员用户时。
  • 您正在测试的场景是什么?管理员登录,删除链接被隐藏,但他以某种方式制作了删除请求? (只是问)
  • 好问题。大多数情况下,答案是这是 Rails 教程中的一个练习。事实证明这是一个很好的练习,因为我学到了关于 cookie、http 请求、Capybara 和 Rack::Test 的各种东西。我想我可能正在测试隐藏删除链接的代码失败并且我希望我的控制器有备份的场景。

标签: ruby-on-rails rspec railstutorial.org


【解决方案1】:

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

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

【讨论】:

  • 我认为这只适用于控制器测试,不适用于集成测试。
  • 这个测试通过了,尽管我手动测试了它并且它不起作用。 (它应该失败)
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    您混淆了作为集成测试并在模拟浏览器中执行的 rspec-rails 请求规范和单独测试控制器的控制器规范。 delete(action, *args)(以及getpost 等) - 是一种模拟来自 ActionController::TestCase 的请求的方法,因此在您的测试中不可用。

    因此,您唯一的选择是在浏览器中模拟点击。我不知道您如何隐藏删除链接,如果 html 存在但隐藏,您应该能够单击它。如果它不存在(生成视图时在服务器端删除),您可以使用水豚的page.execute_script(但您必须为此示例启用javascript:js =&gt; 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'})")
    

    没有测试,但这样的东西应该可以工作。

    【讨论】:

      【解决方案4】:

      这就是我最终得到的结果(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

      【讨论】:

        【解决方案5】:

        试试这个:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-08-26
          • 1970-01-01
          • 1970-01-01
          • 2011-10-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多