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

时间:2011-07-05 08:18:15

标签: ruby-on-rails-3 rspec devise cucumber capybara

我正在使用带有rails 3.0.9,cucumber-rails 1.0.2,capybara 1.0.0的设计1.4.2。点击退出时出现No route matches "/users/sign_out"错误。在经历了这样的问题(no-route-matches-users-sign-out-devise-rails-3)后,我将:method => :delete添加到link_to标记。

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

config.action_view.javascript_expansions[:defaults] = %w(jquery rails)

config.action_view.javascript_expansions[:defaults] = %w(jquery jquery_ujs)

绕过rails.js找不到错误。

虽然进行了上述更改,但我能够成功注销并重定向到root,当我在FireBug中查看localhost:3000 / users / sign_out请求的响应时,它会显示相同的路由错误消息click here to see the screenshot with notes

通过设计成功实现对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  

使用以下step_definition'I退出'

Then /^I sign out$/ do
    visit('/users/sign_out')
end

我搜索了很多,发现这是因为Rails 3中的unobrusive javascript被用于'数据方法'属性,但我也读到了Capybara确实检查数据方法属性并相应行为的地方。但它对我不起作用,所以在这篇文章之后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获得了未定义的方法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没有在sign_out上显示任何错误。但我知道使用'get'请求来销毁会话并不是一个好习惯,因为这是一种改变状态的行为。

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

6 个答案:

答案 0 :(得分:12)

所以我找到了

<%= link_to "Logout", destroy_user_session_path, :method => :delete %>

rails helper生成以下html

<a rel="nofollow" data-method="delete" href="/users/sign_out">Sign out</a>

和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();
}

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

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

<%= button_to "Logout", destroy_user_session_path, :method => :delete %>

生成以下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>

有了这个,我可以在我的“I退出”步骤定义中轻松使用Capybara帮助程序click_button('Logout')。

"link_to with a method anything other than GET is actually a bad idea, as links can be right clicked and opened in a new tab/window, and because this just copies the url (and not the method) it will break for non-get links..."

Max Will explained右键单击并在新标签页中打开与非获取数据方法的link_to链接会导致链接断开。

关于link_to helper和':method =&gt;的更有用的讨论:删除'和capybara问题可以找到on this link

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

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

答案 1 :(得分:8)

解决此问题的最简单方法(尽管可能不是最正确的问题)是修改路由文件以匹配应用程序的其余部分。例如。使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路线。但是,此时以任何其他方式修复它都超出了我的黄瓜知识,因为该套件中的每个测试最终都依赖于访问('/ users / logout'),这对于开箱即用的Devise来说是不可能的。路由。

<强>更新

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

#config.sign_out_via = :delete

答案 2 :(得分:8)

Devise 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一起使用,只需将此更改更改为config/initializers/devise.rb,即可将Rails测试环境中的Devise默认值从DELETE更改为GET:

  

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相同。如果问题未解决或您有更多信息,请在此处添加评论。

答案 3 :(得分:4)

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

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

include Warden::Test::Helpers
Warden.test_mode!

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

答案 4 :(得分:0)

我实际上遇到了同样的问题但是使用了Rails / Sinatra应用程序。我已经为Rails设置了Devise并且注销工作正常。我有一个在lib中运行的GuestApp Sinatra应用程序,除了注销链接外,它运行良好。我试图在sinatra注销链接上强制data-method =“delete”,但我没有做任何事情会使请求成为删除请求。

我认为这对我来说可能是一个sinatra问题,但我认为任何进入的请求都会先由铁路线路处理,直到它们到达我的sinatra路线。我即将手动添加GET路由进行注销,但我宁愿不必这样做。

这是我的设计路线:

devise_for :member, :path => '', :path_names => {
  :sign_in => "login",
  :sign_out => "logout",
  :sign_up => "register" }

这是我的链接:

%a{:href => '/logout', :"data-method" => 'delete', :rel => 'nofollow'}Log Out
<a href="/logout" data-method="delete" rel="nofollow">Log Out</a>

#- realized it should be method instead, but still not reaching routes.rb as delete
<a href="/logout" method="delete" rel="nofollow">Log Out</a>

答案 5 :(得分:0)

当我需要在 test.env 中使用类似的东西时:

listView.setOnItemClickListener(new OnItemClickListener() {

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position,long arg3) {
        view.setSelected(true);
        ... //Anything
    }
})

它为我工作,但也许这是不对的)

配置/ INIT / devise.rb

  visit destroy_user_session_path