¿Por qué factory_girl no opera transaccionalmente para mí? - las filas permanecen en la base de datos después de las pruebas
Frecuentes
Visto 11,403 equipos
34
Estoy tratando de usar factory_girl para crear una fábrica de "usuario" (con RSpec), sin embargo, no parece estar funcionando transaccionalmente y aparentemente está fallando debido a los datos remanentes de pruebas anteriores en la base de datos de prueba.
Factory.define :user do |user|
user.name "Joe Blow"
user.email "joe@blow.com"
user.password 'password'
user.password_confirmation 'password'
end
@user = Factory.create(:user)
Ejecutar el primer conjunto de pruebas está bien:
spec spec/
...
Finished in 2.758806 seconds
60 examples, 0 failures, 11 pending
Todo bien y como se esperaba, sin embargo, ejecutando las pruebas nuevamente:
spec spec/
...
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/validations.rb:1102:in `save_without_dirty!': Validation failed: Email has already been taken (ActiveRecord::RecordInvalid)
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/dirty.rb:87:in `save_without_transactions!'
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/transactions.rb:200:in `save!'
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/database_statements.rb:136:in `transaction'
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/transactions.rb:182:in `transaction'
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/transactions.rb:200:in `save!'
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/transactions.rb:208:in `rollback_active_record_state!'
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/transactions.rb:200:in `save!'
from /Library/Ruby/Gems/1.8/gems/factory_girl-1.2.3/lib/factory_girl/proxy/create.rb:6:in `result'
from /Library/Ruby/Gems/1.8/gems/factory_girl-1.2.3/lib/factory_girl/factory.rb:316:in `run'
from /Library/Ruby/Gems/1.8/gems/factory_girl-1.2.3/lib/factory_girl/factory.rb:260:in `create'
from /Users/petenixey/Rails_apps/resample/spec/controllers/users_controller_spec.rb:7
from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/example/example_group_methods.rb:183:in `module_eval'
from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/example/example_group_methods.rb:183:in `subclass'
from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/example/example_group_methods.rb:55:in `describe'
from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/example/example_group_factory.rb:31:in `create_example_group'
from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/dsl/main.rb:28:in `describe'
from /Users/petenixey/Rails_apps/resample/spec/controllers/users_controller_spec.rb:3
from /Library/Ruby/Gems/1.8/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:147:in `load_without_new_constant_marking'
from /Library/Ruby/Gems/1.8/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:147:in `load'
from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:15:in `load_files'
from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:14:in `each'
from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:14:in `load_files'
from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/runner/options.rb:133:in `run_examples'
from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/lib/spec/runner/command_line.rb:9:in `run'
from /Library/Ruby/Gems/1.8/gems/rspec-1.3.0/bin/spec:5
from /usr/bin/spec:19:in `load'
from /usr/bin/spec:19
Intento de corrección: use Factory.sequence
Como tengo una restricción de unicidad en mi campo de correo electrónico, intenté solucionar el problema utilizando el método de secuencia de factory_girl:
Factory.define :user do |user|
user.name "Joe Blow"
user.sequence(:email) {|n| "joe#{n}@blow.com" }
user.password 'password'
user.password_confirmation 'password'
end
Luego corrí
rake db:test:prepare
spec spec/
.. # running the tests once executes fine
spec spec/
.. # running them the second time produces the same set of errors as before
Los usuarios parecen permanecer en la base de datos.
Si observo la base de datos /db/test.sqlite3, parece que la fila para el usuario de prueba no se revierte de la base de datos entre pruebas. Pensé que se suponía que estas pruebas eran transaccionales, pero no parece que lo sean para mí.
Esto explicaría por qué la prueba se ejecuta correctamente la primera vez (y si borro la base de datos) pero falla la segunda vez.
¿Alguien puede explicar qué debo cambiar para garantizar que las pruebas se ejecuten transaccionalmente?
5 Respuestas
52
Finalmente arreglé esto y espero poder ahorrarle a alguien las seis horas de depuración que me llevó resolverlo.
Por a) tener suerte y terminar con una versión de código que funcionó y b) eliminar ambos conjuntos de código, esto es lo que encontré:
Prueba que se atraganta
require 'spec_helper'
describe UsersController do
@user = Factory.create(:user)
end
Prueba que funciona
require 'spec_helper'
describe UsersController do
it "should make a factory models without choking" do
@user = Factory.create(:user)
end
end
La transacción está definida por el "debería hacer algo" hacer... declaración. Si crea una instancia de la fábrica fuera de esa declaración, resulta que no es transaccional.
También puede ponerlo fuera del bloque "debería..." siempre que esté en un bloque "antes de... terminar".
require 'spec_helper'
describe UsersController do
before(:each) do
@user = Factory.create(:user)
end
it 'should make a factory without choking' do
puts @user.name
# prints out the correnct name for the user
end
end
Al experimentar, parece ser válido definir un usuario fuera de un bloque "debería hacer ... final" siempre que esté en un bloque "antes de ... final". Supongo que esto solo se ejecuta en el ámbito del bloque "debería hacerlo... terminar" y, por lo tanto, funciona bien.
[Gracias a @jdl por su sugerencia (también correcta)]
Respondido el 13 de junio de 11 a las 12:06
¿alguna idea de cómo se comporta esto dentro de un let? así: let(:algo) {Fábrica(:algo)} - Cezar Halmagean
Muy útil, gracias Pedro. César, re. let(:foo) {Factory(:foo)}
, parece que foo
se creará (la fábrica invocada) cuando se haga referencia por primera vez, por lo que es la referencia a foo la que debe aparecer dentro de un ejemplo o un before
cuadra. La base de datos aún se revierte al final del ejemplo. - marca baya
También puedes usar let!
si desea que foo se cree inmediatamente. (Y sí, eso funciona con las transacciones). - Ajedi32
Gracias MarkBerry y Ajedi32, me preguntaba por qué Let no persistía con mi maquinista Foo.make. a la base de datos, pero ponerlo en un bloque anterior sí lo hizo. - poeta montaña
Nunca cree ninguna variable con la que quiera probar fuera de ella, antes o deje que se bloquee. Asignar variables contra las que desea probar (como en su primer ejemplo) es un total no-no. La razón de esto es que las ejecuciones de prueba transaccionales son muy exigentes y ejecutan la configuración y el desmontaje cada vez que se ejecuta CADA PRUEBA. Esto es bueno porque significa que tiene una base de datos limpia. Lo que está haciendo en el primer ejemplo es un no-no (obviamente no funciona) porque crea esos objetos cuando el ARCHIVO SE CARGA (literalmente cuando Ruby lee el bloque 'describir') que es solo una vez al comienzo de todas las pruebas . - Jason FB
21
Vea mi entrada de blog sobre la diferencia entre usar before :all
y before :each
con respecto a las transacciones: http://mwilden.blogspot.com/2010/11/beware-of-rspecs-before-all.html. En una palabra, before :all
no es transaccional, y los datos creados allí permanecerán después de ejecutar la prueba.
Respondido el 08 de diciembre de 10 a las 18:12
Una distinción importante, gracias por esa aclaración. Comprobando test.log, parece que el before :all
todavía comienza una transacción (BEGIN
), pero luego ejecuta un COMMIT
preferible a ROLLBACK
, por lo que los datos persisten. - marca baya
4
In spec/spec_helper.rb
, asegúrese de tener lo siguiente
RSpec.configure do |config|
config.use_transactional_fixtures = true
end
Esto parece resolver el problema para mí.
Respondido el 15 de Septiembre de 11 a las 22:09
3
Dentro de test/test_helper.rb
asegúrese de tener lo siguiente.
class ActiveSupport::TestCase
self.use_transactional_fixtures = true
#...
end
A pesar del nombre "accesorios", esto funciona con factory_girl
.
Respondido 16 ago 10, 22:08
Aunque parece correcto, eso no parece solucionar el problema. Estoy trabajando en RailsTutorial de Michael Hartl y haciendo una segunda escritura del código que escribió originalmente. Cuando lo copié palabra por palabra, todo funcionó bien y sé que nunca agregué el indicador user_transactional_fixtures. Ahora, aunque por alguna razón, está fallando en el proyecto de reescritura, incluso si agrego eso. - pedro nixey
Comentando aquí porque yo también vi el mismo comportamiento desconcertante. De lo contrario, el tuorial fue impresionante. - perry horwich
0
Me encontré con estos mismos síntomas cuando actualicé un proyecto de Rails 3 a Rails 4. Había realizado una instalación de paquete y el modo de desarrollo parecía funcionar bien, pero no obtenía un comportamiento transaccional en las pruebas. resulta que haciendo un actualización del paquete resuelve el problema.
Respondido 12 Jul 13, 01:07
¿Alguna idea de qué gemas actualizaste durante ese bundle update
? - Isaac Betesh
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas ruby-on-rails factory-bot or haz tu propia pregunta.
¿Puedo sugerir que elimine las menciones de factory-girl en la pregunta y la etiqueta? Dado que el problema central realmente se trata de crear registros en el bloque incorrecto. Lo mismo puede suceder si se crea un registro mediante la llamada create() incorporada. - lulalala