【问题标题】:Factory Girl / Capybara deleting records from database mid-test?Factory Girl / Capybara 在测试中从数据库中删除记录?
【发布时间】:2026-01-09 18:35:01
【问题描述】:

与 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 后,没有任何删除。所以我或多或少回到了原点。

【问题讨论】:

    标签: ruby-on-rails rspec2 capybara factory-bot rspec-rails


    【解决方案1】:

    在 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
    

    【讨论】:

    • 最好在spec_helper.rb中设置。
    • 这让我一整天都感到困惑,因为我有很多分心的事情导致我走错了路。谢谢!
    【解决方案2】:

    我认为 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
    

    【讨论】:

    • 我认为用户 variable 被覆盖不是问题,但我会调查一下。谢谢。
    【解决方案3】:

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

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

    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 错误,这是一种不错的快捷方式获得一个干净的会话。

    【讨论】:

    • 嗯,感谢您的提示...我现在对这个问题有了更多了解,但我仍然无法对其进行排序。
    • 您能否发布响应visit '/sessions/index' 的方法的代码,大概是sessions_controller#index(可能值得考虑将其放在sessions/new 顺便说一句)以及您的应用程序控制器。
    • 现在您已经确定问题发生在会话索引中,然后仅使用代码周围的那些puts User.last 调试点应该可以让您放大到用户被删除的确切位置。我不认为是 FactoryGirl 把它拿出来顺便说一句 - 我不认为 FactoryGirl 可以删除东西
    • sessions/index 只是视图,它只是一个表单。不执行控制器动作。我已经填写了 session_controller#login 和 #logout 方法的一些更相关的部分,并添加到 ApplicationController 中。那里没有什么时髦的东西。
    【解决方案4】:

    上面的正确答案帮助了我。当然,我需要更改一些(错误地或正确地)假设夹具不存在的其他测试。更多信息: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

    【讨论】: