Factory Girl / Capybara如何在测试过程中从数据库中删除记录?

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

原测试失败了,登录失败了。在将'debugger'调用插入规范和控制器后,我发现问题所在:根据控制器的认识,用户未被插入到数据库中:

编辑添加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 
为了简洁起见,我省略了用户对象内容的完整转储,但我可以保证测试按预期完成。 在这里交换行以使测试通过的行为与我的想法不太契合,并且已经在其他地方对我的测试造成了相当大的困扰。有什么提示吗? 我搜遍了谷歌和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
[]

所以显然在visit的过程中,某些事情告诉 Factory Girl 它已经完成了对用户对象的操作,因此它将其删除了?

编辑 经过仔细检查 test.log ,没有任何删除操作。 因此,我基本上回到了原点。

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

3
最好将其设置在spec_helper.rb文件中。 - iltempo
1
这让我整天都感到困惑,因为我有很多分心的事情让我走了错误的道路。谢谢! - Rimian

3

我认为在RSpec中的用户变量已经覆盖了控制器中的变量,所以它无法正常工作?(测试中无法获得正确的用户电子邮件)

修改后:

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

我不认为这是用户变量被覆盖的问题,但我会调查一下。谢谢。 - voxobscuro

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错误,这是一种快速获取干净会话的好方法。


好的,谢谢你的提示...我现在对这个问题有了更多的了解,但我仍然无法解决它。 - voxobscuro
你能否发布响应于 visit '/sessions/index' 的方法的代码,预计是 sessions_controller#index(顺便考虑一下将其放在 sessions/new 上)以及你的应用控制器。 - Peter Nixey
现在你已经确定问题发生在sessions index中,那么只需在代码周围使用puts User.last调试点,就可以精确定位用户被删除的位置。我不认为是FactoryGirl将其删除了 - 我不认为FactoryGirl能够删除东西。 - Peter Nixey
sessions/index只是一个视图,而且只是一个表单。没有执行任何控制器操作。我填写了sessions_controller#login和#logout方法的一些更相关的部分,并添加了ApplicationController。里面没有什么奇怪的东西。 - voxobscuro

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接