Rails 3.0.9 + Devise + Cucumber + Capybara臭名昭着的"没有路线匹配/用户/ sign_out"

Zee*_*han 26 rspec cucumber devise capybara ruby-on-rails-3

我正在使用带有rails 3.0.9,cucumber-rails 1.0.2,capybara 1.0.0的设计1.4.2.我有No route matches "/users/sign_out"错误,当我点击注销.:method => :delete经过这样的问题(no-route-matches-users-sign-out-devise-rails-3)后,我添加了link_to标签.

由于我用jquery替换了原型,我也不得不改变

config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
Run Code Online (Sandbox Code Playgroud)

config.action_view.javascript_expansions[:defaults] = %w(jquery jquery_ujs)
Run Code Online (Sandbox Code Playgroud)

绕过rails.js找不到错误.

虽然有了上述更改,但我能够成功注销并重定向到root,当我在FireBug中查看localhost:3000/users/sign_out请求的响应时,它会显示相同的路由错误消息单击此处以查看带有注释的屏幕截图

通过设计成功实现对rails 3 app的身份验证后,当我按照本教程(github.com/RailsApps/rails3-devise-rspec-cucumber/wiki/Tutorial)使用Cucumber + Capybara + RSpec添加功能和规格时,出现以下错误

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)
  <internal:prelude>: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  
Run Code Online (Sandbox Code Playgroud)

使用以下step_definition"我退出"

Then /^I sign out$/ do
    visit('/users/sign_out')
end
Run Code Online (Sandbox Code Playgroud)

我搜索了很多,发现这是因为Rails 3中的unobrusive javascript被用于'数据方法'属性,但我也读到了Capybara确实检查数据方法属性并相应行为的地方.但它对我不起作用,所以在这篇帖子后发生Capybara攻击:机架测试,丢失会话和http请求方法我将步骤定义更改为以下内容:

Then /^I sign out$/ do
    rack_test_session_wrapper = Capybara.current_session.driver
    rack_test_session_wrapper.process :delete, '/users/sign_out'
end
Run Code Online (Sandbox Code Playgroud)

但我得到未定义的方法process进行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
Run Code Online (Sandbox Code Playgroud)

这至少通过了"我退出"步骤,但是在退出后它没有重定向到主页,下一步失败了:

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"'
Run Code Online (Sandbox Code Playgroud)

毕竟,我不得不在路线文件中添加'GET'方法进行注销:

devise_for :users do get 'logout' => 'devise/sessions#destroy' end
Run Code Online (Sandbox Code Playgroud)

修改了我的观点

<%= link_to "Logout", destroy_user_session_path, :method => :delete %>
Run Code Online (Sandbox Code Playgroud)

<%= link_to "Logout", logout_path %>
Run Code Online (Sandbox Code Playgroud)

并将我的步骤定义更改为以下内容:

Then /^I sign out$/ do
    visit('/logout')
end
Run Code Online (Sandbox Code Playgroud)

这显然解决了所有问题,所有测试都通过,firebug没有在sign_out上显示任何错误.但我知道使用'get'请求来销毁会话并不是一个好习惯,因为这是一种改变状态的行为.

这可能是由于我正在使用的特定版本或Rails,Devise,Cucumber-Rails或Capybara?我想使用Devise的默认sign_out路由,而不是使用get方法覆盖它,并且能够使用Cucumber和RSpec进行BDD.我是新手使用Cucumber + Capybara,是否存在另一种方法来发送POST请求而不是使用"visit('/ users/sign_out')",它只使用GET方法?

Zee*_*han 12

所以我发现了

<%= link_to "Logout", destroy_user_session_path, :method => :delete %>
Run Code Online (Sandbox Code Playgroud)

rails helper生成以下html

<a rel="nofollow" data-method="delete" href="/users/sign_out">Sign out</a>
Run Code Online (Sandbox Code Playgroud)

和jquery_ujs.js有以下方法将带有data-method ="delete"属性的链接转换为表单并在运行时提交:

// Handles "data-method" on links such as:
// <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
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 = $('<form method="post" action="' + href + '"></form>'),
metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';
if (csrf_param !== undefined && csrf_token !== undefined) {
metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
}
form.hide().append(metadata_input).appendTo('body');
form.submit();
}
Run Code Online (Sandbox Code Playgroud)

和Capybara帮助访问('/ users/sign_out')只需单击链接并向服务器发送GET请求,该服务器没有任何此请求的路由.

与link_to helper相反,button_to帮助器在呈现页面时在html中添加所需的表单,而不是依赖于javascript:

<%= button_to "Logout", destroy_user_session_path, :method => :delete %>
Run Code Online (Sandbox Code Playgroud)

生成以下html

<form class="button_to" action="/users/sign_out" method="post">
    <div>
        <input type="hidden" name="_method" value="delete">
        <input type="submit" value="Logout">
        <input type="hidden" name="authenticity_token" value="0Il8D+7hRcWYfl7A1MjNPenDixLYZUkMBL4OOoryeJs=">
    </div>
</form>
Run Code Online (Sandbox Code Playgroud)

有了这个,我可以在我的'I sign out'步骤定义中轻松使用Capybara helper click_button('Logout').

"使用除GET之外的任何方法的link_to实际上是一个坏主意,因为可以在新的选项卡/窗口中右键单击并打开链接,并且因为这只是复制url(而不是方法),它将为非get获取链接..."

正如Max Will所说,右键单击并在新选项卡中打开带有非获取数据方法的link_to链接会导致链接断开.

可以在此链接上找到关于 link_to helper与':method =>:delete'和capybara问题的更有用的讨论

现在我会坚持使用简单的link_to helper而不使用:method属性,如果我想切换到非get方法进行删除,我更喜欢使用button_to.

同时我认为应该有一个相当于访问的水豚助手来满足数据方法属性发送帖子请求,这样就可以避免使用基于javascript的驱动程序进行集成测试.或者可能已经有一个我不知道的.如果我错了,请纠正我.

  • 您对我如何在http://github.com/RailsApps/rails3-devise-rspec-cucumber/wiki/Tutorial上改进教程有任何建议,以便其他人不会遇到此问题吗? (2认同)

cai*_*nne 8

解决此问题的最简单方法(尽管可能不是最正确的问题)是修改路由文件以匹配应用程序的其余部分.例如,使destroy_user_session_path的GET版本工作.您可以通过修改routes文件来执行此操作,如下所示

去掉:

devise_for :users
Run Code Online (Sandbox Code Playgroud)

加:

devise_for :users do
  get "/users/sign_out" => "devise/sessions#destroy", :as => :destroy_user_session
end
Run Code Online (Sandbox Code Playgroud)

这有点脏.我确信Devise有理由弃用GET路线.但是,此时以任何其他方式修复它都超出了我的黄瓜知识,因为该套件中的每个测试最终都依赖于访问('/ users/logout'),这对开箱即用的Devise来说是不可能的.路线.

UPDATE

您还可以通过在config/initialers/devise.rb中注释掉以下内容来解决此问题

#config.sign_out_via = :delete
Run Code Online (Sandbox Code Playgroud)


Dan*_*hoe 8

设计1.4.1(2011年6月27日)更改了注销请求的默认行为:

https://github.com/plataformatec/devise/commit/adb127bb3e3b334cba903db2c21710e8c41c2b40

Jose Valim解释了原因:"GET请求不应该改变服务器的状态.当注销是GET请求时,CSRF可以用来自动注销,而预加载链接的东西最终也会错误地签出你."

Cucumber想要测试GET请求而不是DELETE对destroy_user_session_path的请求.如果您打算将Cucumber与Devise一起使用,只需将此更改更改为Rails测试环境,将Devise默认值从DELETE更改为GET config/initializers/devise.rb:

config.sign_out_via = Rails.env.test??:get :: delete

不要尝试调整routes.rb文件来进行修复.没有必要.如果您不打算使用Cucumber,请保留Devise的新默认值(DELETE).

这里的示例源代码:

https://github.com/RailsApps/rails3-devise-rspec-cucumber

现在包括对Cucumber的Devise初始化程序的更改.

这里的应用模板:

https://github.com/RailsApps/rails3-application-templates

现在检测Devise和Cucumber之间的碰撞,并根据需要改变Devise初始化程序.

使用Rails 3.1.0.rc4测试了这些更改,但行为应该与Rails 3.0.9相同.如果问题未解决或您有更多信息,请在此处添加评论.


eve*_*lli 5

解决此问题的正确方法在设备的 wiki 页面中进行了说明:https : //github.com/plataformatec/devise/wiki/How-To : -Test-with-Capybara

基本上,一旦你包含在你的 user_step.rb 文件中:

include Warden::Test::Helpers
Warden.test_mode!
Run Code Online (Sandbox Code Playgroud)

您可以替换visit '/users/sign_out'logout(:user)