14

与 RSpec 和 Capybara 一起工作,我得到了一个有趣的测试失败模式,它会随着测试用例中的一些细微的行重新排列而消失......这应该不重要。

我正在开发自己的身份验证系统。它目前正在运行,我可以使用浏览器登录/退出,并且会话可以正常工作等等。但是,尝试测试它失败了。发生了一些我不太明白的事情,这似乎取决于(看似)不相关的调用的顺序。

require 'spec_helper'

describe "Sessions" do
  it 'allows user to login' do
    #line one
    user = Factory(:user)
    #For SO, this method hashes the input password and saves the record
    user.password! '2468'

    #line two
    visit '/sessions/index'


    fill_in 'Email', :with => user.email
    fill_in 'Password', :with => '2468'
    click_button 'Sign in'

    page.should have_content('Logged in')
  end
end

照原样,该测试失败......登录失败。在将“调试器”调用插入规范和控制器后,我可以看到原因:就控制器而言,用户没有被插入数据库:

在 ApplicationController 中编辑添加

class ApplicationController < ActionController::Base
  helper :all
  protect_from_forgery

  helper_method :user_signed_in?, :guest_user?, :current_user

  def user_signed_in?
    !(session[:user_id].nil? || current_user.new_record?)
  end

  def guest_user?
    current_user.new_record?
  end

  def current_user
    @current_user ||= session[:user_id].nil? ? User.new : User.find(session[:user_id])
  rescue ActiveRecord::RecordNotFound
    @current_user = User.new
    flash[:notice] = 'You\'ve been logged out.'
  end
end


class SessionsController < ApplicationController
  def login
    user = User.where(:email=>params[:user][:email]).first

    debugger ###

    if !user.nil? && user.valid_password?(params[:user][:password])
      #engage session
    else
      #run away
    end
  end

  def logout
    reset_session
    redirect_to root_path, :notice => 'Logget Out.'
  end
end

在控制台中,在上述断点处:

1.9.2 vox@Alpha:~/Sites/website$ rspec spec/controllers/sessions_controller_spec.rb 
/Users/vox/Sites/website/app/controllers/sessions_controller.rb:7
if !user.nil? && user.valid_password?(params[:user][:password])
(rdb:1) irb
ruby-1.9.2-p180 :001 > User.all.count
 => 0 
ruby-1.9.2-p180 :002 > 

但是,如果我在测试中重新排列几行,将“二”行放在“一”行上方:

describe "Sessions" do
  it 'allows user to login' do
    #line two
    visit '/sessions/index'

    #line one
    user = Factory(:user)
    #For SO, this method hashes the input password and saves the record
    user.password! '2468'


    fill_in 'Email', :with => user.email
    fill_in 'Password', :with => '2468'
    click_button 'Sign in'

    page.should have_content('Logged in')
  end
end

我在控制台中得到这个(与上面相同的断点):

1.9.2 vox@Alpha:~/Sites/website$ rspec spec/controllers/sessions_controller_spec.rb 
/Users/vox/Sites/website/app/controllers/sessions_controller.rb:7
if !user.nil? && user.valid_password?(params[:user][:password])
(rdb:1) irb
ruby-1.9.2-p180 :001 > User.all.count
 => 1 

为简洁起见,我省略了用户对象内容的完整转储,但我可以向您保证测试按预期完成。

这种通过换行来让测试通过的行为并不符合我对这些命令应该如何处理的想法,并且已证明对我在其他领域的测试非常不利。

关于这里发生了什么的任何提示?

我已经在 google 和 SO 上搜索了提出这个问题的想法,并且不乏关于 RSpec/Capybara 和 Sessions 的 SO 问题。不过,似乎没有什么合适的。

感谢您的关注。

更新

我在测试中添加了一个断点(就在访问调用之前)和一些调试,然后返回:

(rdb:1) user
#<User id: 1, login_count: 1, email: "testuser1@website.com", encrypted_password: "11f40764d011926eccd5a102c532a2b469d8e71249f3c6e2f8b...", salt: "1313613794">
(rdb:1) User.all
[#<User id: 1, login_count: 1, email: "testuser1@website.com", encrypted_password: "11f40764d011926eccd5a102c532a2b469d8e71249f3c6e2f8b...", salt: "1313613794">]
(rdb:1) next
/Users/vox/Sites/website/spec/controllers/sessions_controller_spec.rb:19
fill_in 'Email', :with => user.email
(rdb:1) User.all
[]

很明显,访问过程中的某些事情是告诉 Factory Girl 用户对象已完成,因此她将其删除?

编辑仔细检查 test.log 后,没有发出任何删除。所以我或多或少地回到了第一方。

4

4 回答 4

23

在 Factory Girl 邮件列表的帮助下,我找到了问题所在。

默认情况下,RSpec 使用事务将数据库保持在干净状态,并且每个事务都绑定到一个线程。在管道的某处,visit_page 命令分离,与当前线程相关的事务终止。

解决方案很简单:禁用事务。

describe "Sessions" do
  self.use_transactional_fixtures = false

   it 'no longer uses transactions' do
     #whatever you want
  end
end

Rails 5.1 更新

从 Rails 5.1 开始,use_transactional_fixtures已弃用,应替换为use_transactional_tests.

self.use_transactional_tests = false
于 2011-08-18T19:10:45.943 回答
3

我认为 RSpec 中的用户变量已经覆盖了控制器中的用户变量,所以它不起作用?(在测试中无法获得正确的 user.email)

前 :

user = Factory(:user)
user.password! '2468'

visit '/sessions/index' # user gets overwritten

fill_in 'Email', :with => user.email # can't get user.email

后 :

visit '/sessions/index' # Execute action

user = Factory(:user) # user gets overwritten
user.password! '2468'

fill_in 'Email', :with => user.email  # user.email works
于 2011-08-16T22:54:03.537 回答
1

这在技术上不是一个答案,更多的是评论,但为了澄清代码,这是最简单的机制。

您可以尝试执行以下操作来帮助缩小用户被破坏的范围

describe "Sessions" do
  it 'allows user to login' do
    #line one
    user = Factory(:user)
    #For SO, this method hashes the input password and saves the record
    user.password! '2468'


# check the user's definitely there before page load
puts User.first

    #line two
    visit '/sessions/index'

# check the user's still there after page load
puts User.first.reload


    fill_in 'Email', :with => user.email
    fill_in 'Password', :with => '2468'
    click_button 'Sign in'

# check the user's still there on submission (though evidently not)
puts User.first.reload

    page.should have_content('Logged in')
  end
end

编辑

它在现实生活中对你有效,但在 Capybara 中无效,这一事实表明它可能是现有会话信息的产物。当您在浏览器中进行测试时,您通常会脱离以前的工作,但 Capybara 总是从干净的会话开始。

您可以通过清除所有 cookie(我相信您知道)或仅切换到 Chrome/FF 中的新隐身窗口来轻松查看是否可以在浏览器中重现 Capybara 错误,这是一种快速获取干净的会话。

于 2011-08-16T23:13:50.040 回答
0

上面的正确答案帮助了我。当然,我需要更改一些(错误或正确地)假设夹具不存在的其他测试。有关更多信息:Capybara README 中有一些关于此的信息。

https://github.com/jnicklas/capybara

“如果您使用的是 SQL 数据库,通常会在事务中运行每个测试,在测试结束时回滚,例如,rspec-rails 默认情况下会执行此操作。由于事务通常不跨线程共享,这将导致您在测试代码中放入数据库的数据对 Capybara 不可见。”

您还可以手动配置 RSpec 以在测试后进行清理:

https://github.com/jnicklas/carrierwave/wiki/How-to%3A-Cleanup-after-your-Rspec-tests

于 2013-04-15T01:15:13.893 回答