Rails 3.0.9 + Devise + Cucumber + Capybara 中出现了臭名昭著的 "No route matches /users/sign_out" 错误。
Rails 3.0.9 + Devise + Cucumber + Capybara 中出现了臭名昭著的 "No route matches /users/sign_out" 错误。
我使用devise 1.4.2与rails 3.0.9、cucumber-rails 1.0.2、capybara 1.0.0。当我点击注销时,出现了“No route matches \"/users/sign_out\"
”错误。我在link_to标签中添加了:method => :delete
,参考了这个问题(no-route-matches-users-sign-out-devise-rails-3)。
由于我用jquery替换了prototype,我还需要将
config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
替换为
config.action_view.javascript_expansions[:defaults] = %w(jquery jquery_ujs)
以解决rails.js未找到的错误。
虽然我通过上述更改成功注销了并重定向到根目录,但是当我在FireBug中查看localhost:3000/users/sign_out请求的响应时,它显示了相同的路由错误消息。点击此处查看带注释的屏幕截图
成功通过devise为rails 3应用程序实现身份验证后,当我使用Cucumber + Capybara + RSpec添加功能和规范时,使用此教程(github.com/RailsApps/rails3-devise-rspec-cucumber/wiki/Tutorial),我遇到了以下错误:
When I sign in as "user@test.com/please" # features/step_definitions/user_steps.rb:41 Then I should be signed in # features/step_definitions/user_steps.rb:49 And I sign out # features/step_definitions/user_steps.rb:53 No route matches "/users/sign_out" (ActionController::RoutingError) :10:in `synchronize' ./features/step_definitions/user_steps.rb:55:in `/^I sign out$/' features/users/sign_out.feature:10:in `And I sign out' And I should see "Signed out" # features/step_definitions/web_steps.rb:105 When I return next time # features/step_definitions/user_steps.rb:60 Then I should be signed out
使用以下“我注销”步骤定义
Then /^I sign out$/ do visit('/users/sign_out') end
我搜索了很多,发现这是由于Rails 3中的未侵入式javascript使用\"data-method\"属性,但我也在某个地方读到Capybara确实检查\"data-method\"属性并相应地运行。但是这对我没有用,因此在阅读这篇文章Capybara attack: rack-test, lost sessions and http request methods后我将我的步骤定义更改为以下内容:
Then /^I sign out$/ do rack_test_session_wrapper = Capybara.current_session.driver rack_test_session_wrapper.process :delete, '/users/sign_out' end
但是,我得到了未定义的方法process
for Capybara::RackTest::Driver (NoMethodError)
错误。
根据此操作,我将上述步骤定义更改为以下内容:
Then /^I sign out$/ do rack_test_session_wrapper = Capybara.current_session.driver rack_test_session_wrapper.delete '/users/sign_out' end
至少此步骤通过了“我注销”,但是在注销后没有重定向到主页,下一步失败了:
And I should see "Signed out" # features/step_definitions/web_steps.rb:105 expected there to be content "Signed out" in "YasPiktochart\n\n \n Signed in as user@test.com. Not you?\n Logout\n \n\n Signed in successfully.\n\n Home\n User: user@test.com\n\n\n\n" (RSpec::Expectations::ExpectationNotMetError) ./features/step_definitions/web_steps.rb:107:in `/^(?:|I )should see "([^"]*)"$/' features/users/sign_out.feature:11:in `And I should see "Signed out"'
最后,我不得不在路由文件中添加“GET”方法来注销:
devise_for :users do get 'logout' => 'devise/sessions#destroy' end
我修改了我的视图从
<%= link_to "Logout", destroy_user_session_path, :method => :delete %>
到
<%= link_to "Logout", logout_path %>
并将我的步骤定义更改为以下内容:
Then /^I sign out$/ do visit('/logout') end
这显然解决了所有问题,所有测试都通过了,并且firebug在注销时没有显示任何错误。但是我知道使用“GET”请求销毁会话不是一种好的做法,因为它是一种改变状态的行为。
这可能是由于我使用的特定版本或Rails,Devise,Cucumber-Rails或Capybara?我想使用Devise的默认sign_out路线,而不是使用GET方法进行覆盖,并能够使用Cucumber和RSpec进行BDD。我是使用Cucumber + Capybara的新手,除了使用“visit(\'/users/sign_out\')”使用GET方法之外,是否还存在另一种发送POST请求的方法?
修复这个问题最简单的方法(尽管可能不是最正确的方法)是修改您的路由文件以匹配应用的其余部分。例如,使destroy_user_session_path的GET版本起作用。您可以通过修改路由文件来完成此操作:
删除:
devise_for :users
添加:
devise_for :users do get "/users/sign_out" => "devise/sessions#destroy", :as => :destroy_user_session end
这有点糟糕。我相信Devise出于良好的原因已弃用了GET路线。但是,以任何其他方式修复它都超出了我的Cucumber知识范围,因为测试套件中的每个测试最终都依赖于visit('/users/logout'),这在开箱即用的Devise路由中是不可能的。
更新
您还可以通过注释config/initialers/devise.rb中的以下内容来修复此问题:
#config.sign_out_via = :delete
所以我发现:
<%= link_to "Logout", destroy_user_session_path, :method => :delete %>
rails助手生成以下html代码:
Sign out
而jquery_ujs.js有以下方法,将带有"data-method='delete'"属性的链接转换为表单并在运行时提交:
// Handles "data-method" on links such as: // Delete handleMethod: function(link) { var href = link.attr('href'), method = link.data('method'), csrf_token = $('meta[name=csrf-token]').attr('content'), csrf_param = $('meta[name=csrf-param]').attr('content'), form = $(''), metadata_input = ''; if (csrf_param !== undefined && csrf_token !== undefined) { metadata_input += ''; } form.hide().append(metadata_input).appendTo('body'); form.submit(); }
Capybara助手visit('/users/sign_out')只需单击该链接并向服务器发送GET请求,服务器没有任何路由可以处理此请求。
与link_to助手不同,button_to助手在渲染页面时添加所需的表单,而不是依赖于javascript:
<%= button_to "Logout", destroy_user_session_path, :method => :delete %>
生成以下HTML代码:
通过这种方式,我可以轻松地在我的'I sign out'步骤定义中使用Capybara助手click_button('Logout')。
“使用任何其他方法的link_to实际上是一个不好的主意,因为链接可以被右键单击并在新的选项卡/窗口中打开,因为这只是复制URL(而不是方法),它将破坏非get链接...”
正如Max Will解释的那样,右键单击非get数据方法的link_to链接并在新标签页中打开会导致链接损坏。
关于带有':method => :delete'的link_to助手和capybara问题的更多有用讨论可以在此链接上找到。
目前,我会坚持使用简单的link_to助手而不使用:method属性,并且如果我想切换到非get方法进行删除,我会更喜欢使用button_to。
同时,我认为应该有一个Capybara助手相当于Visit,以满足数据方法属性以发送post请求,以便可以避免使用基于javascript的驱动程序进行集成测试。或者可能已经有一个我不知道的,如果我错了,请纠正我。