工厂女孩/水豚在测试中从数据库中删除记录?
与 RSpec 和 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
为了简洁起见,我省略了的完整转储用户对象的内容,但我可以向您保证测试按预期完成。
这种交换线路以使测试通过的行为并不真正符合我对这些命令应该发生的事情的想法,并且已被证明对我在其他领域的测试非常不利。
关于这里发生的事情有什么提示吗?
我已经在谷歌和SO中搜索了提出这个问题的想法,并且不乏关于RSpec/Capybara和Sessions的SO问题。但似乎没有什么是完全合适的。
感谢您的关注。
更新
我在测试中添加了一个断点(就在访问调用之前)和一些调试,然后返回:
(rdb:1) user
#<User id: 1, login_count: 1, email: "[email protected]", encrypted_password: "11f40764d011926eccd5a102c532a2b469d8e71249f3c6e2f8b...", salt: "1313613794">
(rdb:1) User.all
[#<User id: 1, login_count: 1, email: "[email protected]", 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后,没有任何内容发出任何删除。所以我或多或少又回到了第一个方面。
Working with RSpec & Capybara, I'm getting an interesting test failure mode which goes away with a few subtle rearrangements of lines in the test case...stuff that shouldn't matter.
I'm developing my own authentication system. It is currently working and I can login/out with the browser and the session works etc etc. However, trying to test this is failing. Something is going on that I don't quite understand, which seems to depend on the order of (seemingly) unrelated calls.
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
As is, that test fails...the login fails. After inserting 'debugger' calls into both the spec and the controller I can see why: the user is not getting inserted into the database as far as the controller is concerned:
Edit adding in 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
in the console, at the above breakpoint:
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 >
However, if I rearrange a few lines in my test, putting line 'two' above line 'one':
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
I get this in the console (same breakpoint as above):
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
For the sake of brevity I've omitted the full dump of the contents of the user object but I can assure you that the test completes as expected.
This behavior of swapping lines to get the test to pass doesn't really fit well with my idea of what should be going on with these commands and has proven to be quite a bear to my testing in other areas.
Any hints as to what is going on here?
I've scoured google and SO for ideas which present this problem, and there are no shortage of SO questions about RSpec/Capybara and Sessions. Nothing seemed to fit quite right though.
Thanks for looking.
Update
I've added a breakpoint (just before a visit call) and some debugging to the test and come back with this:
(rdb:1) user
#<User id: 1, login_count: 1, email: "[email protected]", encrypted_password: "11f40764d011926eccd5a102c532a2b469d8e71249f3c6e2f8b...", salt: "1313613794">
(rdb:1) User.all
[#<User id: 1, login_count: 1, email: "[email protected]", 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
[]
So clearly something along the way that visit does is telling Factory Girl that its done with the user object and so she deletes it?
Edit After inspecting test.log carefully, nothing is issuing any delete. So I'm more or less back to square one.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
在 Factory Girl 邮件列表的帮助下,我发现了这个问题。
默认情况下,RSpec 使用事务来维护数据库处于干净状态,并且每个事务都绑定到一个线程。沿着管道的某个地方,visit_page 命令会分离,并且与当前线程相关的事务会终止。
解决方案很简单:禁用事务。
Rails 5.1 更新
从 Rails 5.1 开始,
use_transactional_fixtures
已弃用,应替换为use_transactional_tests
。With the help of the Factory Girl mailing list I've found the issue.
By default RSpec uses transactions to maintain the database in a clean state and each transaction is tied to a thread. Somewhere along the pipeline the visit_page command splits off and the transaction tied to the current thread dies.
The solution is simple: disable transactions.
Update for Rails 5.1
As of Rails 5.1,
use_transactional_fixtures
is deprecated and should be replaced withuse_transactional_tests
.我认为 RSpec 中的用户变量已经覆盖了控制器中的用户变量,因此它不起作用? (在测试中无法获得正确的 user.email)
之前:
之后:
I think the user variable in RSpec has overwritten the one in the controller so it didn't work ? (couldn't get right user.email in the test)
Before :
After :
从技术上讲,这不是一个答案,更多的是一个评论,但为了澄清代码,这是最简单的机制。
您可以尝试执行以下操作来帮助缩小用户被破坏的范围
编辑
它在现实生活中对您有效但在水豚中无效这一事实表明它可能现有会话信息的产物。当您在浏览器中进行测试时,您通常会结束之前的工作,但 Capybara 总是从干净的会话开始。
您可以轻松地查看是否可以通过清除所有 cookie(我相信您知道)或在 Chrome/FF 中切换到新的隐身窗口来重现浏览器中的水豚错误,这是一种快速获取错误的好方法。干净的会话。
This isn't technically an answer, more of a comment but to clarify the code it's the easiest mechanism.
Can you try doing the following to help narrow down where the user's being destroyed
EDIT
The fact that it works for you ok in real life but not in Capybara suggests that it may be a product of existing session information. When you're testing in the browser you're usually going off the back of previous work but Capybara is always starting from a clean session.
You can easily see if you can reproduce the Capybara error in-browser by clearing all your cookies (as I'm sure you know) or by just switching to a new incognito window in Chrome/FF which is a nice quick way to get a clean session.
上面的正确答案对我有帮助。当然,我需要更改一些其他测试(错误或正确)假设夹具不存在。有关更多信息:Capybara README 中有一些关于此的信息。
https://github.com/jnicklas/capybara
“如果您使用的是 SQL 数据库,则很常见例如,要运行事务中的每个测试(在测试结束时回滚),rspec-rails 默认情况下会执行此操作,因为事务通常不会跨线程共享,这将导致您放入的数据。测试代码中的数据库不可见到水豚。”
您还可以将 RSpec 配置为在测试后手动进行清理:
https://github.com/jnicklas/rierwave/wiki/How-to%3A-Cleanup-after-your-Rspec-tests
The correct answer above helped me. Of course, I needed to change some other tests that (wrongly or rightly) assumed a fixture did not exist. For more information: there's some info about this in the Capybara README.
https://github.com/jnicklas/capybara
"If you are using a SQL database, it is common to run every test in a transaction, which is rolled back at the end of the test, rspec-rails does this by default out of the box for example. Since transactions are usually not shared across threads, this will cause data you have put into the database in your test code to be invisible to Capybara."
You can also config RSpec to clean up after your test manually:
https://github.com/jnicklas/carrierwave/wiki/How-to%3A-Cleanup-after-your-Rspec-tests