34 votes

Pourquoi factory_girl ne fonctionne pas de manière transactionnelle pour moi ? - Des 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 il ne semble pas fonctionner de manière transactionnelle et échoue apparemment à cause 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)

L'exécution de la première série de tests se passe bien :

spec spec/ 

...
Finished in 2.758806 seconds

60 examples, 0 failures, 11 pending

Tout est bon et conforme aux attentes, mais je recommence 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

Tentative de correction - utiliser Factory.sequence

Comme j'ai une contrainte d'unicité sur mon champ email, j'ai essayé 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 ensuite 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 de test ne soit pas supprimée de la base de données entre les tests. Je pensais que ces tests étaient censés être transactionnels, mais il semble que ce ne soit pas le cas pour moi.

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

Quelqu'un peut-il m'expliquer ce que je dois changer pour que les tests s'exécutent de manière transactionnelle ?

Merci.

52voto

Peter Nixey Points 4153

J'ai enfin résolu le problème et j'espère pouvoir épargner à quelqu'un les six heures de débogage qu'il m'a fallu pour le résoudre.

En a) ayant de la chance et en finissant avec une version du code qui fonctionnait et b) en dépouillant les deux ensembles de code, voici ce que j'ai trouvé :

Test qui s'étouffe

require 'spec_helper'

describe UsersController do

  @user = Factory.create(:user) 
end

Un 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 il "devrait faire quelque chose" faire... déclaration. Si vous instanciez la fabrique en dehors de cette déclaration, il s'avère qu'elle n'est pas transactionnelle.

Vous pouvez également le mettre en dehors du bloc "il devrait " tant qu'il est dans un bloc "avant fin".

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

En expérimentant, il semble qu'il soit valide de définir un utilisateur en dehors d'un bloc "il devrait faire fin" tant que c'est dans un bloc "avant fin". Je suppose que cela n'est exécuté que dans le cadre du bloc "it should do..end" et que cela fonctionne donc bien.

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

21voto

Mark Wilden Points 855

Voir mon article de blog sur la différence entre l'utilisation de before :all y 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 resteront en place après l'exécution du test.

4voto

Chris Points 118

En spec/spec_helper.rb assurez-vous que vous disposez des éléments suivants

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

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

3voto

jdl Points 12272

A l'intérieur de test/test_helper.rb assurez-vous que vous disposez des éléments suivants.

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

Malgré le nom "fixtures", cela fonctionne avec factory_girl également.

0voto

mgrant Points 40

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 développement semblait fonctionner correctement, mais je n'obtenais pas de comportement transactionnel dans les tests. Il s'avère que faire une mise à jour du forfait a résolu le problème.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X