Question Pourquoi est-ce que factory_girl ne fonctionne pas de manière transactionnelle pour moi? - les lignes restent dans la base de données après les tests


J'essaie d'utiliser factory_girl pour créer une usine "utilisateur" (avec RSpec), mais elle ne semble pas fonctionner de manière transactionnelle et semble échouer en raison des données restantes des tests précédents dans la base de données de test.

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)

La première série de tests est correcte:

spec spec/ 


...
Finished in 2.758806 seconds

60 examples, 0 failures, 11 pending

Tout va bien et comme prévu, cependant en exécutant à nouveau les tests:

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

Correction de la tentative - utilisez Factory.sequence

Comme j'ai une contrainte d'unicité sur mon champ de messagerie, j'ai tenté de résoudre le problème en utilisant la méthode de séquence 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

J'ai alors couru

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

Les utilisateurs semblent rester dans la base de données

Si je regarde la base de données /db/test.sqlite3, il semble que la ligne de l'utilisateur test ne soit pas annulée de la base de données entre les tests. Je pensais que ces tests étaient supposés être transactionnels mais ils ne semblent pas l'être pour moi.

Cela expliquerait pourquoi le test s'exécute correctement la première fois (et si j'efface la base de données) mais échoue la deuxième fois.

Quelqu'un peut-il expliquer ce que je devrais changer pour assurer que les tests sont exécutés de manière transactionnelle?


34
2017-08-16 20:40


origine


Réponses:


Finalement, j'ai résolu ce problème et j'espère pouvoir sauver quelqu'un des six heures de débogage qu'il m'a fallu pour comprendre.

En a) avoir de la chance et finir avec une version du code qui a fonctionné et b) dépouiller les deux jeux de code, voici ce que j'ai trouvé:

Test qui étouffe

require 'spec_helper'

describe UsersController do

  @user = Factory.create(:user) 
end

Test qui fonctionne

require 'spec_helper'

describe UsersController do

  it "should make a factory models without choking" do
    @user = Factory.create(:user)   
  end
end

La transaction est définie par le ça "devrait faire quelque chose" fait ... déclaration. Si vous instanciez la fabrique en dehors de cette instruction, elle ne sera pas transactionnelle.

Vous pouvez également le placer en dehors du bloc "it should .." tant qu'il se trouve dans un bloc "before..end"

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

Lors de l'expérimentation, il semble être valide de définir un utilisateur en dehors d'un bloc "it do do do..end" tant qu'il se trouve dans un bloc "before .. end". J'imagine que cela ne s'exécute que dans le cadre du bloc "it do do do..end" et fonctionne donc correctement.

[Merci à @jdl pour sa suggestion (également correcte)]


52
2017-08-17 02:45



Voir mon blog sur la différence entre utiliser before :all et before :each en ce qui concerne les transactions: http://mwilden.blogspot.com/2010/11/beware-of-rspecs-before-all.html. En un mot, before :all n'est pas transactionnel et les données créées à cet endroit persisteront après l'exécution du test.


21
2017-12-08 18:43



Dans spec/spec_helper.rb, assurez-vous d'avoir les éléments suivants

RSpec.configure do |config|
  config.use_transactional_fixtures = true
end

Cela semble résoudre le problème pour moi.


4
2017-09-15 21:51



À l'intérieur de test/test_helper.rb assurez-vous que vous avez les éléments suivants.

class ActiveSupport::TestCase
  self.use_transactional_fixtures = true
  #...
end

Malgré le nom "fixtures" cela fonctionne avec factory_girl ainsi que.


3
2017-08-16 21:16



J'ai rencontré ces mêmes symptômes lors de la mise à niveau d'un projet de Rails 3 à Rails 4. J'avais fait une installation groupée et le mode de développement semblait fonctionner correctement, mais je n'obtiendrais pas de comportement transactionnel dans les tests. Il s'avère que faire un mise à jour groupée résolu le problème.


0
2017-07-12 00:57