- Mettre à jour votre Gemfile
- Configurer votre suite de tests
- Test: :Unit
- Cucumber
- Spinach
- Minitest
- Minitest: :Spec
- minitest-rails
- Définir les fabriques
- Utilisation des factories
- Attributs dynamiques
- Aliases
- Attributs dépendants
- Attributs transitoires
- Nom de méthode / Attributs de mots réservés
- Héritage
- Associations
- Séquences
- Traits
- Callbacks
- Modifier les usines
- Construction ou création de plusieurs enregistrements
- Linting Factories
- Custom Construction
- Stratégies personnalisées
- Custom Callbacks
- Méthodes personnalisées pour la persistance des objets
- ActiveSupport Instrumentation
- Rails Preloaders and RSpec
- Utilisation sans Bundler
Mettre à jour votre Gemfile
Si vous utilisez Rails, vous devrez changer la version requise de factory_girl_rails
:
gem "factory_girl_rails", "~> 4.0"
Si vous n’utilisez pas Rails, vous devrez juste changer la version requise de factory_girl
:
gem "factory_girl", "~> 4.0"
export JRUBY_OPTS=--1.9
Une fois votre Gemfile mis à jour, vous voudrez mettre à jour votre bundle.
Configurer votre suite de tests
N’oubliez pas de requérir le fichier ci-dessus dans votre rails_helper puisque le dossier support n’est pas eagerly chargé
require 'support/factory_girl'
Test: :Unit
class Test::Unit::TestCase include FactoryGirl::Syntax::Methodsend
Cucumber
# env.rb (Rails example location - RAILS_ROOT/features/support/env.rb)World(FactoryGirl::Syntax::Methods)
Spinach
class Spinach::FeatureSteps include FactoryGirl::Syntax::Methodsend
Minitest
class Minitest::Unit::TestCase include FactoryGirl::Syntax::Methodsend
Minitest: :Spec
class Minitest::Spec include FactoryGirl::Syntax::Methodsend
minitest-rails
class ActiveSupport::TestCase include FactoryGirl::Syntax::Methodsend
Si vous n’incluez pas FactoryGirl::Syntax::Methods
dans votre suite de tests, alors toutes les méthodes factory_girl devront être préfacées par FactoryGirl
.
Définir les fabriques
Chaque fabrique a un nom et un ensemble d’attributs. Le nom est utilisé pour deviner la classe de l’objet par défaut, mais il est possible de le spécifier explicitement :
Il est fortement recommandé d’avoir une fabrique pour chaque classe qui fournit l’ensemble le plus simple d’attributs nécessaires pour créer une instance de cette classe. Si vous créez des objets ActiveRecord, cela signifie que vous ne devez fournir que les attributs qui sont requis par les validations et qui n’ont pas de valeurs par défaut. D’autres fabriques peuvent être créées par héritage pour couvrir les scénarios communs à chaque classe.
Tenter de définir plusieurs fabriques avec le même nom entraînera une erreur.
Les factories peuvent être définies n’importe où, mais seront automatiquement chargées après l’appel de FactoryGirl.find_definitions
si les factories sont définies dans des fichiers aux emplacements suivants :
test/factories.rbspec/factories.rbtest/factories/*.rbspec/factories/*.rb
Utilisation des factories
factory_girl supporte plusieurs stratégies de construction différentes : build, create, attributes_for et build_stubbed :
Quelle que soit la stratégie utilisée, il est possible de remplacer les attributs définis en passant un hash :
# Build a User instance and override the first_name propertyuser = build(:user, first_name: "Joe")user.first_name# => "Joe"
Attributs dynamiques
La plupart des attributs de fabrique peuvent être ajoutés en utilisant des valeurs statiques qui sont évaluées lorsque la fabrique est définie, mais certains attributs (comme les associations et d’autres attributs qui doivent être générés dynamiquement) auront besoin de valeurs attribuées chaque fois qu’une instance est générée. Ces attributs « dynamiques » peuvent être ajoutés en passant un bloc au lieu d’un paramètre:
factory :user do # ... activation_code { User.generate_activation_code } date_of_birth { 21.years.ago }end
En raison de la syntaxe de bloc en Ruby, la définition d’attributs comme Hash
es (pour des colonnes sérialisées/JSON, par exemple) nécessite deux ensembles de crochets curly:
factory :program do configuration { { auto_resolve: false, auto_define: true } }end
Aliases
factory_girl vous permet de définir des alias aux usines existantes pour les rendre plus faciles à réutiliser. Cela peut s’avérer pratique lorsque, par exemple, votre objet Post possède un attribut author qui fait en réalité référence à une instance d’une classe User. Alors que normalement factory_girl peut déduire le nom de la fabrique à partir du nom de l’association, dans ce cas, il cherchera en vain une fabrique d’auteur. Donc, alias votre user factory pour qu’elle puisse être utilisée sous des noms d’alias.
Attributs dépendants
Les attributs peuvent être basés sur les valeurs d’autres attributs en utilisant l’evaluatorth qui est cédé aux blocs d’attributs dynamiques:
Attributs transitoires
Il peut y avoir des moments où votre code peut être DRYed up en passant des attributs transitoires aux factories.
Les attributs statiques et dynamiques peuvent être créés comme des attributs transitoires. Les attributs transitoires seront ignorés dans attributes_for et ne seront pas définis sur le modèle,même si l’attribut existe ou si vous tentez de le remplacer.
Dans les attributs dynamiques de factory_girl, vous pouvez accéder aux attributs transitoires comme vous pouvez vous y attendre. Si vous devez accéder à l’évaluateur dans un callback de factory_girl, vous devrez déclarer un deuxième argument de bloc (pour l’évaluateur) et accéder aux attributs transitoires à partir de là.
Nom de méthode / Attributs de mots réservés
Si vos attributs entrent en conflit avec des méthodes ou des mots réservés existants, vous pouvez les définir avec add_attribute
.
factory :dna do add_attribute(:sequence) { 'GATTACA' }endfactory :payment do add_attribute(:method) { 'paypal' }end
Héritage
Vous pouvez facilement créer plusieurs fabriques pour la même classe sans répéter les attributs communs en imbriquant les fabriques :
Vous pouvez également assigner le parent explicitement :
factory :post do title "A title"endfactory :approved_post, parent: :post do approved trueend
Comme mentionné ci-dessus, c’est une bonne pratique de définir une fabrique de base pour chaque classeavec seulement les attributs requis pour la créer. Ensuite, créez des fabriques plus spécifiques qui héritent de ce parent de base. Les définitions de fabrique sont toujours du code, donc gardez-les DRY.
Associations
Il est possible de mettre en place des associations au sein des fabriques. Si le nom de la fabrique est le même que celui de l’association, le nom de la fabrique peut être laissé de côté.
factory :post do # ... authorend
Vous pouvez également spécifier une fabrique différente ou des attributs de surcharge:
factory :post do # ... association :author, factory: :user, last_name: "Writely"end
Le comportement de la méthode d’association varie en fonction de la stratégie de construction utilisée pour l’objet parent.
Pour ne pas sauvegarder l’objet associé, spécifiez la stratégie : :build dans la fabrique:
Veuillez noter que l’option strategy: :build
doit être passée à un appel explicite à association
,et ne peut pas être utilisée avec des associations implicites:
factory :post do # ... author strategy: :build # <<< this does *not* work; causes author_id to be nil
Générer des données pour une relation has_many
est un peu plus impliqué,selon la quantité de flexibilité désirée, mais voici un exemple infaillible de génération de données associées.
FactoryGirl.define do # post factory with a `belongs_to` association for the user factory :post do title "Through the Looking Glass" user end # user factory without associated posts factory :user do name "John Doe" # user_with_posts will create post data after the user has been created factory :user_with_posts do # posts_count is declared as a transient attribute and available in # attributes on the factory, as well as the callback via the evaluator transient do posts_count 5 end # the after(:create) yields two values; the user instance itself and the # evaluator, which stores all values from the factory, including transient # attributes; `create_list`'s second argument is the number of records # to create and we make sure the user is associated properly to the post after(:create) do |user, evaluator| create_list(:post, evaluator.posts_count, user: user) end end endend
Cela nous permet de faire :
create(:user).posts.length # 0create(:user_with_posts).posts.length # 5create(:user_with_posts, posts_count: 15).posts.length # 15
Générer des données pour une relation has_and_belongs_to_many
est très similaire à la relation has_many
ci-dessus, avec un petit changement, vous devez passer un tableau d’objets au nom d’attribut pluralisé du modèle plutôt qu’un seulobjet à la version singulière du nom d’attribut.
Voici un exemple avec deux modèles qui sont reliés viahas_and_belongs_to_many
:
FactoryGirl.define do # language factory with a `belongs_to` association for the profile factory :language do title "Through the Looking Glass" profile end # profile factory without associated languages factory :profile do name "John Doe" # profile_with_languages will create language data after the profile has # been created factory :profile_with_languages do # languages_count is declared as an ignored attribute and available in # attributes on the factory, as well as the callback via the evaluator transient do languages_count 5 end # the after(:create) yields two values; the profile instance itself and # the evaluator, which stores all values from the factory, including # ignored attributes; `create_list`'s second argument is the number of # records to create and we make sure the profile is associated properly # to the language after(:create) do |profile, evaluator| create_list(:language, evaluator.languages_count, profiles: ) end end endend
Cela nous permet de faire:
Séquences
Des valeurs uniques dans un format spécifique (par exemple, des adresses e-mail) peuvent être générées en utilisant des séquences. Les séquences sont définies en appelant sequence
dans un bloc adefinition, et les valeurs d’une séquence sont générées en appelantgenerate
:
Les séquences peuvent être utilisées dans des attributs dynamiques:
factory :invite do invitee { generate(:email) }end
Ou comme attributs implicites :
factory :user do email # Same as `email { generate(:email) }`end
Et il est également possible de définir une séquence en ligne qui n’est utilisée que dans une usine particulière:
factory :user do sequence(:email) { |n| "person#{n}@example.com" }end
Vous pouvez également remplacer la valeur initiale:
factory :user do sequence(:email, 1000) { |n| "person#{n}@example.com" }end
Sans bloc, la valeur s’incrémentera d’elle-même, en commençant à sa valeur initiale:
factory :post do sequence(:position)end
Les séquences peuvent également avoir des alias. Les alias de séquence partagent le même compteur:
Définir les alias et utiliser la valeur par défaut (1) pour le compteur
factory :user do sequence(:email, aliases: ) { |n| "person#{n}@example.com" }end
Définir la valeur:
factory :user do sequence(:email, 'a', aliases: ) { |n| "person#{n}@example.com" }end
La valeur doit juste supporter la méthode #next
. Ici, la prochaine valeur sera ‘a’, puis ‘b’, etc.
Traits
Les traits vous permettent de regrouper des attributs et de les appliquer ensuite à n’importe quelle factory.
Les traits peuvent être utilisés comme attributs:
Les traits qui définissent les mêmes attributs ne lèveront pas d’AttributeDefinitionErrors;le trait qui définit l’attribut le plus tard a la priorité.
Vous pouvez également surcharger les attributs individuels accordés par un trait dans les sous-classes.
Les traits peuvent également être passés comme une liste de symboles lorsque vous construisez une instance de factory_girl.
Cette capacité fonctionne avec build
, build_stubbed
, attributes_for
, et create
.
create_list
et build_list
méthodes sont également supportées. N’oubliez pas de passer le nombre d’instances à créer/construire comme deuxième paramètre, comme documenté dans la section « Construction ou création d’enregistrements multiples » de ce fichier.
Les traits peuvent être utilisés avec les associations facilement aussi:
Lorsque vous utilisez des noms d’association qui sont différents de la fabrique:
Les traits peuvent être utilisés dans d’autres traits pour mélanger leurs attributs.
factory :order do trait :completed do completed_at { 3.days.ago } end trait :refunded do completed refunded_at { 1.day.ago } endend
Enfin, les traits peuvent accepter des attributs transitoires.
Callbacks
factory_girl met à disposition quatre callbacks pour injecter du code:
Exemples:
Notez que vous aurez une instance de l’utilisateur dans le bloc. Cela peut être utile.
Vous pouvez également définir plusieurs types de callbacks sur la même factory:
factory :user do after(:build) { |user| do_something_to(user) } after(:create) { |user| do_something_else_to(user) }end
Les factory peuvent également définir un nombre quelconque de callbacks du même type. Ces callbacks seront exécutés dans l’ordre où ils sont spécifiés:
factory :user do after(:create) { this_runs_first } after(:create) { then_this }end
Appeler create
invoquera à la fois les callbacks after_build
et after_create
.
De plus, comme les attributs standards, les usines enfants hériteront (et pourront aussi définir) des callbacks de leur usine parent.
Des callbacks multiples peuvent être affectés à l’exécution d’un bloc ; ceci est utile lors de la construction de diverses stratégies qui exécutent le même code (puisqu’il n’y a pas de callbacks qui sont partagés entre toutes les stratégies).
Pour surcharger les callbacks pour toutes les usines, définissez-les dans le blocFactoryGirl.define
:
Vous pouvez aussi appeler les callbacks qui s’appuient sur Symbol#to_proc
:
Modifier les usines
Si on vous donne un ensemble d’usines (disons, d’un développeur de gemmes) mais que vous voulez les changer pour mieux les adapter à votre application, vous pouvez modifier cette usine au lieu de créer une usine enfant et d’y ajouter des attributs.
Si une gem vous donnait une fabrique d’utilisateur :
FactoryGirl.define do factory :user do full_name "John Doe" sequence(:username) { |n| "user#{n}" } password "password" endend
Au lieu de créer une fabrique enfant qui ajoute des attributs supplémentaires :
Vous pourriez modifier cette fabrique à la place.
FactoryGirl.modify do factory :user do full_name "Jane Doe" date_of_birth { 21.years.ago } gender "Female" health 90 endend
Lorsque vous modifiez une fabrique, vous pouvez changer tous les attributs que vous voulez (à part les callbacks).
FactoryGirl.modify
doit être appelé en dehors d’un bloc FactoryGirl.define
car il opère sur les usines différemment.
Un avertissement : vous ne pouvez modifier que les usines (pas les séquences ou les traits) et les callbacks continuent à composer comme ils le feraient normalement. Ainsi, si la fabrique que vous modifiez définit un callback after(:create)
, vous définissant un after(:create)
ne le remplacera pas, il sera juste exécuté après le premier callback.
Construction ou création de plusieurs enregistrements
Parfois, vous voudrez créer ou construire plusieurs instances d’une fabrique à la fois.
built_users = build_list(:user, 25)created_users = create_list(:user, 25)
Ces méthodes construiront ou créeront une quantité spécifique de fabriques et les retourneront sous forme de tableau.Pour définir les attributs de chacune des fabriques, vous pouvez passer dans un hachage comme vous le feriez normalement.
twenty_year_olds = build_list(:user, 25, date_of_birth: 20.years.ago)
build_stubbed_list
vous donnera des instances complètement stubées:
stubbed_users = build_stubbed_list(:user, 25) # array of stubbed users
Il y a aussi un ensemble de méthodes *_pair
pour créer deux enregistrements à la fois:
built_users = build_pair(:user) # array of two built userscreated_users = create_pair(:user) # array of two created users
Si vous avez besoin de plusieurs hachages d’attributs, attributes_for_list
les générera :
users_attrs = attributes_for_list(:user, 25) # array of attribute hashes
Linting Factories
factory_girl permet de linting des usines connues:
FactoryGirl.lint
FactoryGirl.lint
crée chaque usine et attrape toutes les exceptions soulevées lors du processus de création. FactoryGirl::InvalidFactoryError
est soulevée avec une liste de fabriques (et les exceptions correspondantes) pour les fabriques qui n’ont pas pu être créées.
L’utilisation recommandée de FactoryGirl.lint
est de l’exécuter dans une tâche avant que votre suite de tests soit exécutée.L’exécuter dans une before(:suite)
,aura un impact négatif sur les performances de vos testslors de l’exécution de tests uniques.
Exemple de tâche Rake:
Après avoir appelé FactoryGirl.lint
, vous voudrez probablement vider la base de données, car des enregistrements seront très probablement créés. L’exemple fourni ci-dessus utilise la gemme database_cleaner pour effacer la base de données ; assurez-vous d’ajouter la gemme à votre Gemfile sous les groupes appropriés.
Vous pouvez lint les usines sélectivement en passant seulement les usines que vous voulez linted:
factories_to_lint = FactoryGirl.factories.reject do |factory| factory.name =~ /^old_/endFactoryGirl.lint factories_to_lint
Cela lint toutes les usines qui ne sont pas préfixées par old_
.
Les traits peuvent également être linted. Cette option vérifie que chaque trait d’une fabrique génère un objet valide par lui-même.Elle est activée en passant traits: true
à la méthode lint
:
FactoryGirl.lint traits: true
Elle peut également être combinée avec d’autres arguments:
FactoryGirl.lint factories_to_lint, traits: true
Vous pouvez également spécifier la stratégie utilisée pour le linting :
FactoryGirl.lint strategy: :build
Custom Construction
Si vous voulez utiliser factory_girl pour construire un objet où certains attributs sont passés à initialize
ou si vous voulez faire autre chose que simplement appeler new
sur votre classe de construction, vous pouvez surcharger le comportement par défaut en définissant initialize_with
sur votre factory. Exemple:
Bien que factory_girl soit écrite pour fonctionner avec ActiveRecord dès le départ, elle peut également fonctionner avec n’importe quelle classe Ruby. Pour une compatibilité maximale avec ActiveRecord,l’initialisateur par défaut construit toutes les instances en appelant new
sur votre classe de construction sans aucun argument. Il appelle ensuite des méthodes d’écriture d’attributs pour attribuer toutes les valeurs d’attributs. Bien que cela fonctionne bien pour ActiveRecord, cela ne fonctionne en fait pas pour presque toutes les autres classes Ruby.
Vous pouvez surcharger l’initialisateur afin de :
- Construire des objets non-ActiveRecord qui nécessitent des arguments pour
initialize
- Utiliser une méthode autre que
new
pour instancier l’instance - Faire des choses folles comme décorer l’instance après qu’elle soit construite
Lorsque vous utilisez initialize_with
, vous n’avez pas à déclarer la classe elle-même en appelant new
; cependant, toutes les autres méthodes de classe que vous voulez appeler devront être appelées sur la classe explicitement.
Par exemple:
factory :user do name "John Doe" initialize_with { User.build_with_name(name) }end
Vous pouvez également accéder à tous les attributs publics dans le bloc initialize_with
en appelant attributes
:
factory :user do transient do 5 end name "John Doe" initialize_with { new(attributes) }end
Cela construira un hachage de tous les attributs à passer à new
. Il n’inclura pas les attributs transitoires, mais tout le reste défini dans la fabrique sera passé (associations, séquences évaluées, etc.)
Vous pouvez définir initialize_with
pour toutes les fabriques en l’incluant dans le blocFactoryGirl.define
:
FactoryGirl.define do initialize_with { new("Awesome first argument") }end
Lorsqu’on utilise initialize_with
, les attributs auxquels on accède depuis l’intérieur du bloc initialize_with
ne sont assignés que dans le constructeur ; cela équivaut à peu près au code suivant:
FactoryGirl.define do factory :user do initialize_with { new(name) } name { 'value' } endendbuild(:user)# runsUser.new('value')
Ceci empêche l’assignation en double ; dans les versions de factory_girl avant 4.0, il s’exécuterait ainsi:
Stratégies personnalisées
Il y a des moments où vous pouvez vouloir étendre le comportement de factory_girl en ajoutant une stratégie de construction personnalisée.
Les stratégies définissent deux méthodes : association
et result
. association
Reçoit une instance FactoryGirl::FactoryRunner
, sur laquelle vous pouvez appelerrun
, en surchargeant la stratégie si vous le souhaitez. La deuxième méthode, result
, reçoit une instance FactoryGirl::Evaluation
. Elle fournit un moyen de déclencher des callbacks (avec notify
), object
ou hash
(pour obtenir l’instance de résultat ou un ahash basé sur les attributs définis dans la factory), et create
, qui exécute le callback to_create
défini sur la factory.
Pour comprendre comment factory_girl utilise les stratégies en interne, il est probablement plus simple de simplement visualiser la source de chacune des quatre stratégies par défaut.
Voici un exemple de composition d’une stratégie utilisantFactoryGirl::Strategy::Create
pour construire une représentation JSON de votre modèle.
Pour que factory_girl reconnaisse la nouvelle stratégie, vous pouvez l’enregistrer :
FactoryGirl.register_strategy(:json, JsonStrategy)
Cela vous permet d’appeler
FactoryGirl.json(:user)
Finalement, vous pouvez surcharger les propres stratégies de factory_girl si vous le souhaitez en enregistrant un nouvel objet à la place des stratégies.
Custom Callbacks
Des callbacks personnalisés peuvent être définis si vous utilisez des stratégies personnalisées :
Méthodes personnalisées pour la persistance des objets
Par défaut, la création d’un enregistrement appellera save!
sur l’instance ; puisque ceci n’est pas toujours idéal, vous pouvez surcharger ce comportement en définissantto_create
sur la fabrique:
factory :different_orm_model do to_create { |instance| instance.persist! }end
Pour désactiver complètement la méthode de persistance sur la création, vous pouvez skip_create
pour cette fabrique :
factory :user_without_database do skip_createend
Pour surcharger to_create
pour toutes les usines, définissez-le dans le blocFactoryGirl.define
:
FactoryGirl.define do to_create { |instance| instance.persist! } factory :user do name "John Doe" endend
ActiveSupport Instrumentation
Afin de suivre quelles usines sont créées (et avec quelle stratégie de construction),ActiveSupport::Notifications
sont inclus pour fournir un moyen de s’abonner aux usines en cours d’exécution. Un exemple serait de suivre les fabriques basées sur un seuil de temps d’exécution.
Un autre exemple serait de suivre toutes les fabriques et comment elles sont utilisées à travers votre suite de tests. Si vous utilisez RSpec, il suffit d’ajouter unebefore(:suite)
et une after(:suite)
:
Rails Preloaders and RSpec
Lorsque vous exécutez RSpec avec un préchargeur Rails tel que spring
ou zeus
, il est possiblede rencontrer une erreur ActiveRecord::AssociationTypeMismatch
lors de la création d’une fabrique avec des associations, comme ci-dessous :
L’erreur se produit pendant l’exécution de la suite de tests :
Les deux solutions possibles sont soit d’exécuter la suite sans le préchargeur, soit d’ajouter FactoryGirl.reload
à la configuration RSpec, comme suit :
RSpec.configure do |config| config.before(:suite) { FactoryGirl.reload }end
Utilisation sans Bundler
Si vous n’utilisez pas Bundler, assurez-vous d’avoir la gemme installée et appelez:
require 'factory_girl'
Une fois requis, en supposant que vous avez une structure de répertoire de spec/factories
outest/factories
, tout ce que vous aurez à faire est d’exécuter
FactoryGirl.find_definitions
Si vous utilisez une structure de répertoire distincte pour vos usines, vous pouvez changer les chemins des fichiers de définition avant d’essayer de trouver des définitions :
FactoryGirl.definition_file_paths = %w(custom_factories_directory)FactoryGirl.find_definitions
Si vous n’avez pas de répertoire séparé de fabriques et que vous souhaitez les définir en ligne, c’est également possible :
require 'factory_girl'FactoryGirl.define do factory :user do name 'John Doe' date_of_birth { 21.years.ago } endend
.