¿Por qué factory_girl no opera transaccionalmente para mí? - las filas permanecen en la base de datos después de las pruebas

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?

preguntado el 16 de agosto de 10 a las 17:08

¿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. -

5 Respuestas

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

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

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

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

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 or haz tu propia pregunta.