Rails 3.0.9 + Devise + Cucumber + Capybara 中出现了臭名昭著的 "No route matches /users/sign_out" 错误。

26 浏览
0 Comments

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请求的方法?

admin 更改状态以发布 2023年5月19日
0
0 Comments

修复这个问题最简单的方法(尽管可能不是最正确的方法)是修改您的路由文件以匹配应用的其余部分。例如,使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

0
0 Comments

所以我发现:

<%= 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的驱动程序进行集成测试。或者可能已经有一个我不知道的,如果我错了,请纠正我。

0