- Aggiorna il tuo Gemfile
- Configura la tua suite di test
- Test::Unit
- Cucumber
- Spinach
- Minitest
- Minitest::Spec
- minitest-rails
- Definizione delle fabbriche
- Usare le fabbriche
- Attributi dinamici
- Alias
- Attributi dipendenti
- Attributi transitori
- Nome del metodo / Attributi di parole riservate
- Eredità
- Associazioni
- Sequenze
- Traits
- Callbacks
- Modificare le fabbriche
- Costruire o creare record multipli
- Linting delle fabbriche
- Costruzione personalizzata
- Strategie personalizzate
- Callback personalizzati
- Metodi personalizzati per persistere gli oggetti
- ActiveSupport Instrumentation
- Preloader Rails e RSpec
- Usando senza Bundler
Aggiorna il tuo Gemfile
Se stai usando Rails, dovrai cambiare la versione richiesta di factory_girl_rails
:
gem "factory_girl_rails", "~> 4.0"
Se non stai usando Rails, dovrai solo cambiare la versione richiesta di factory_girl
:
gem "factory_girl", "~> 4.0"
export JRUBY_OPTS=--1.9
Una volta aggiornato il tuo Gemfile, dovrai aggiornare il tuo bundle.
Configura la tua suite di test
Ricordati di richiedere il file di cui sopra nel tuo rails_helper poiché la cartella di supporto non è caricata in modo avido
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
Se non includete FactoryGirl::Syntax::Methods
nella vostra suite di test, allora tutti i metodi factory_girl dovranno essere preceduti da FactoryGirl
.
Definizione delle fabbriche
Ogni fabbrica ha un nome e un insieme di attributi. Il nome è usato per indovinare la classe dell’oggetto per default, ma è possibile specificarlo esplicitamente:
Si raccomanda vivamente di avere una factory per ogni classe che fornisca l’insieme più semplice di attributi necessari per creare un’istanza di quella classe. Se stai creando oggetti ActiveRecord, ciò significa che dovresti fornire solo gli attributi che sono richiesti attraverso le validazioni e che non hanno default. Altre fabbriche possono essere create attraverso l’ereditarietà per coprire scenari comuni per ogni classe.
Tentando di definire più fabbriche con lo stesso nome verrà generato un errore.
Le fabbriche possono essere definite ovunque, ma saranno caricate automaticamente dopo aver chiamato FactoryGirl.find_definitions
se le fabbriche sono definite in file nelle seguenti posizioni:
test/factories.rbspec/factories.rbtest/factories/*.rbspec/factories/*.rb
Usare le fabbriche
factory_girl supporta diverse strategie di costruzione: build, create, attributes_for e build_stubbed:
Non importa quale strategia sia usata, è possibile sovrascrivere gli attributi definiti passando un hash:
# Build a User instance and override the first_name propertyuser = build(:user, first_name: "Joe")user.first_name# => "Joe"
Attributi dinamici
La maggior parte degli attributi di fabbrica possono essere aggiunti usando valori statici che sono valutati quando la fabbrica è definita, ma alcuni attributi (come le associazioni e altri attributi che devono essere generati dinamicamente) avranno bisogno di valori assegnati ogni volta che un’istanza viene generata. Questi attributi “dinamici” possono essere aggiunti passando un blocco invece di un parametro:
factory :user do # ... activation_code { User.generate_activation_code } date_of_birth { 21.years.ago }end
A causa della sintassi dei blocchi in Ruby, definire attributi come Hash
es (per colonne serializzate/JSON, per esempio) richiede due serie di parentesi graffe:
factory :program do configuration { { auto_resolve: false, auto_define: true } }end
Alias
factory_girl ti permette di definire alias a factory esistenti per renderle più facili da riutilizzare. Questo potrebbe essere utile quando, per esempio, il tuo oggetto Post ha un attributo author che in realtà si riferisce ad un’istanza della classe User. Mentre normalmente factory_girl può dedurre il nome del factory dal nome dell’associazione, in questo caso cercherà invano un factory autore. Quindi, date un alias alla vostra fabbrica utente in modo che possa essere usata sotto nomi di alias.
Attributi dipendenti
Gli attributi possono essere basati sui valori di altri attributi usando il valutatore che viene fornito ai blocchi di attributi dinamici:
Attributi transitori
Ci possono essere momenti in cui il vostro codice può essere DRYED up passando attributi transitori alle fabbriche.
Gli attributi statici e dinamici possono essere creati come attributi transitori. Gli attributi transitori saranno ignorati all’interno di attributes_for e non saranno impostati sul modello, anche se l’attributo esiste o si tenta di sovrascriverlo.
Negli attributi dinamici di factory_girl, si può accedere agli attributi transitori come ci si aspetterebbe. Se avete bisogno di accedere al valutatore in una callback di factory_girl, dovrete dichiarare un secondo argomento di blocco (per il valutatore) e accedere agli attributi transitori da lì.
Nome del metodo / Attributi di parole riservate
Se i vostri attributi sono in conflitto con metodi esistenti o parole riservate, potete definirli con add_attribute
.
factory :dna do add_attribute(:sequence) { 'GATTACA' }endfactory :payment do add_attribute(:method) { 'paypal' }end
Eredità
Puoi facilmente creare più factory per la stessa classe senza ripetere attributi comuni annidando le factory:
Puoi anche assegnare il genitore esplicitamente:
factory :post do title "A title"endfactory :approved_post, parent: :post do approved trueend
Come detto sopra, è buona pratica definire una factory di base per ogni classe con solo gli attributi richiesti per crearla. Poi, creare factory più specifiche che ereditano da questo genitore di base. Le definizioni di fabbrica sono ancora codice, quindi mantenetele DRY.
Associazioni
È possibile impostare associazioni all’interno delle fabbriche. Se il nome della fabbrica è lo stesso dell’associazione, il nome della fabbrica può essere omesso.
factory :post do # ... authorend
Puoi anche specificare una fabbrica diversa o sovrascrivere gli attributi:
factory :post do # ... association :author, factory: :user, last_name: "Writely"end
Il comportamento del metodo dell’associazione varia a seconda della strategia di costruzione usata per l’oggetto padre.
Per non salvare l’oggetto associato, specificare la strategia: :build nella fabbrica:
Si noti che l’opzione strategy: :build
deve essere passata a una chiamata esplicita a association
, e non può essere usata con associazioni implicite:
factory :post do # ... author strategy: :build # <<< this does *not* work; causes author_id to be nil
Generare dati per una relazione has_many
è un po’ più complesso, a seconda della quantità di flessibilità desiderata, ma ecco un esempio sicuro di generazione di dati associati.
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
Questo ci permette di fare:
create(:user).posts.length # 0create(:user_with_posts).posts.length # 5create(:user_with_posts, posts_count: 15).posts.length # 15
Generare dati per una relazione has_and_belongs_to_many
è molto simile alla precedente relazione has_many
, con un piccolo cambiamento, è necessario passare una serie di oggetti al nome dell’attributo pluralizzato del modello piuttosto che un singolo oggetto alla versione singola del nome dell’attributo.
Ecco un esempio con due modelli correlati tramitehas_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
Questo ci permette di fare:
Sequenze
I valori unici in un formato specifico (per esempio, gli indirizzi e-mail) possono essere generati usando sequenze. Le sequenze sono definite chiamando sequence
nel blocco di definizione, e i valori in una sequenza sono generati chiamandogenerate
:
Le sequenze possono essere usate in attributi dinamici:
factory :invite do invitee { generate(:email) }end
O come attributi impliciti:
factory :user do email # Same as `email { generate(:email) }`end
E’ anche possibile definire una sequenza in linea che è usata solo in una particolare fabbrica:
factory :user do sequence(:email) { |n| "person#{n}@example.com" }end
Puoi anche sovrascrivere il valore iniziale:
factory :user do sequence(:email, 1000) { |n| "person#{n}@example.com" }end
Senza un blocco, il valore aumenterà da solo, partendo dal suo valore iniziale:
factory :post do sequence(:position)end
Le sequenze possono anche avere alias. Gli alias delle sequenze condividono lo stesso contatore:
Definire gli alias e usare il valore predefinito (1) per il contatore
factory :user do sequence(:email, aliases: ) { |n| "person#{n}@example.com" }end
Impostare il valore:
factory :user do sequence(:email, 'a', aliases: ) { |n| "person#{n}@example.com" }end
Il valore deve solo supportare il metodo #next
. Qui il prossimo valore sarà ‘a’, poi ‘b’, ecc.
Traits
ITraits ti permettono di raggruppare gli attributi e poi applicarli a qualsiasi factory.
I traits possono essere usati come attributi:
Traits che definiscono gli stessi attributi non solleveranno AttributeDefinitionErrors; il trait che definisce l’ultimo attributo ha la precedenza.
Puoi anche sovrascrivere singoli attributi concessi da un tratto nelle sottoclassi.
I tratti possono anche essere passati come lista di simboli quando costruisci un’istanza da factory_girl.
Questa capacità funziona con build
, build_stubbed
, attributes_for
e create
.
create_list
e build_list
sono supportati anche i metodi. Ricordati solo di passare il numero di istanze da creare/costruire come secondo parametro, come documentato nella sezione “Costruire o creare record multipli” di questo file.
I tratti possono essere usati facilmente anche con le associazioni:
Quando stai usando nomi di associazioni che sono diversi dalla fabbrica:
I tratti possono essere usati all’interno di altri tratti per mescolare i loro attributi.
factory :order do trait :completed do completed_at { 3.days.ago } end trait :refunded do completed refunded_at { 1.day.ago } endend
Infine, i tratti possono accettare attributi transitori.
Callbacks
factory_girl rende disponibili quattro callback per iniettare del codice:
Esempi:
Nota che avrai un’istanza dell’utente nel blocco. Questo può essere utile.
Puoi anche definire più tipi di callback sulla stessa fabbrica:
factory :user do after(:build) { |user| do_something_to(user) } after(:create) { |user| do_something_else_to(user) }end
Le fabbriche possono anche definire qualsiasi numero dello stesso tipo di callback. Queste callback saranno eseguite nell’ordine in cui sono specificate:
factory :user do after(:create) { this_runs_first } after(:create) { then_this }end
Chiamando create
saranno invocate entrambe le callback after_build
e after_create
.
Inoltre, come gli attributi standard, le fabbriche figlie ereditano (e possono anche definire) callback dalla loro fabbrica madre.
Multipli callback possono essere assegnati per eseguire un blocco; questo è utile quando si costruiscono varie strategie che eseguono lo stesso codice (dato che non ci sono callback condivisi tra tutte le strategie).
Per sovrascrivere le callback per tutte le fabbriche, definiscile all’interno del bloccoFactoryGirl.define
:
Puoi anche chiamare callback che si basano su Symbol#to_proc
:
Modificare le fabbriche
Se ti viene dato un insieme di fabbriche (per esempio, da uno sviluppatore di gemme) ma vuoi cambiarle per adattarle meglio alla tua applicazione, puoi modificare quella fabbrica invece di creare una fabbrica figlia e aggiungerci gli attributi.
Se una gemma vi desse una fabbrica Utente:
FactoryGirl.define do factory :user do full_name "John Doe" sequence(:username) { |n| "user#{n}" } password "password" endend
Invece di creare una fabbrica figlia che aggiunga ulteriori attributi:
Potreste invece modificare quella fabbrica.
FactoryGirl.modify do factory :user do full_name "Jane Doe" date_of_birth { 21.years.ago } gender "Female" health 90 endend
Quando modificate una fabbrica, potete cambiare tutti gli attributi che volete (a parte i callback).
FactoryGirl.modify
deve essere chiamato al di fuori di un blocco FactoryGirl.define
perché opera sulle fabbriche in modo diverso.
Un avvertimento: potete modificare solo le fabbriche (non le sequenze o i tratti) e le callback continuano a comporre come farebbero normalmente. Quindi, se la fabbrica che stai modificando definisce un callback after(:create)
, definendo un after(:create)
non lo sovrascriverai, verrà semplicemente eseguito dopo il primo callback.
Costruire o creare record multipli
A volte, vorrai creare o costruire istanze multiple di una fabbrica in una volta sola.
built_users = build_list(:user, 25)created_users = create_list(:user, 25)
Questi metodi costruiranno o creeranno un numero specifico di factory e li restituiranno come array.per impostare gli attributi per ciascuna delle factory, puoi passare un hash come faresti normalmente.
twenty_year_olds = build_list(:user, 25, date_of_birth: 20.years.ago)
build_stubbed_list
vi darà istanze completamente stubbate:
stubbed_users = build_stubbed_list(:user, 25) # array of stubbed users
C’è anche una serie di metodi *_pair
per creare due record alla volta:
built_users = build_pair(:user) # array of two built userscreated_users = create_pair(:user) # array of two created users
Se avete bisogno di più hash di attributi, attributes_for_list
li genererà:
users_attrs = attributes_for_list(:user, 25) # array of attribute hashes
Linting delle fabbriche
factory_girl permette il linting delle fabbriche note:
FactoryGirl.lint
FactoryGirl.lint
crea ogni fabbrica e cattura qualsiasi eccezione sollevata durante il processo di creazione. FactoryGirl::InvalidFactoryError
viene sollevato con una lista di fabbriche (e le eccezioni corrispondenti) per le fabbriche che non possono essere create.
L’uso raccomandato di FactoryGirl.lint
è di eseguirlo in un task prima che la vostra suite di test venga eseguita.Eseguirlo in un before(:suite)
, avrà un impatto negativo sulle prestazioni dei vostri test quando si eseguono test singoli.
Esempio di task Rake:
Dopo aver chiamato FactoryGirl.lint
, probabilmente vorrete svuotare il database, dato che molto probabilmente verranno creati dei record. L’esempio fornito sopra usa la gemma database_cleaner per ripulire il database; assicurati di aggiungere la gemma al tuo Gemfile sotto i gruppi appropriati.
Puoi fare il linting dei factory selettivamente passando solo i factory che vuoi listare:
factories_to_lint = FactoryGirl.factories.reject do |factory| factory.name =~ /^old_/endFactoryGirl.lint factories_to_lint
Questo linting di tutti i factory che non hanno il prefisso old_
.
I tratti possono anche essere lincerati. Questa opzione verifica che ogni tratto di una fabbrica generi da solo un oggetto valido.Si attiva passando traits: true
al metodo lint
:
FactoryGirl.lint traits: true
Questo può anche essere combinato con altri argomenti:
FactoryGirl.lint factories_to_lint, traits: true
Si può anche specificare la strategia usata per il linting:
FactoryGirl.lint strategy: :build
Costruzione personalizzata
Se vuoi usare factory_girl per costruire un oggetto in cui alcuni attributi sono passati a initialize
o se vuoi fare qualcosa di diverso dal chiamare semplicemente new
sulla tua classe di build, puoi sovrascrivere il comportamento predefinito definendo initialize_with
sulla tua factory. Esempio:
Anche se factory_girl è scritto per funzionare con ActiveRecord, può anche funzionare con qualsiasi classe Ruby. Per la massima compatibilità con ActiveRecord, l’inizializzatore predefinito costruisce tutte le istanze chiamando new
sulla tua classe di costruzione senza alcun argomento. Poi chiama i metodi di scrittura degli attributi per assegnare tutti i valori degli attributi. Mentre questo funziona bene per ActiveRecord, in realtà non funziona per quasi tutte le altre classi Ruby.
Puoi sovrascrivere l’inizializzatore per:
- Costruire oggetti non ActiveRecord che richiedono argomenti per
initialize
- Utilizzare un metodo diverso da
new
per istanziare l’istanza - Fare cose folli come decorare l’istanza dopo che è stata costruita
Quando usi initialize_with
, non devi dichiarare la classe stessa quando chiami new
; tuttavia, qualsiasi altro metodo di classe che volete chiamare dovrà essere chiamato esplicitamente sulla classe.
Per esempio:
factory :user do name "John Doe" initialize_with { User.build_with_name(name) }end
Puoi anche accedere a tutti gli attributi pubblici all’interno del blocco initialize_with
chiamando attributes
:
factory :user do transient do 5 end name "John Doe" initialize_with { new(attributes) }end
Questo costruirà un hash di tutti gli attributi da passare a new
. Non includerà gli attributi transitori, ma tutto il resto definito nel factory sarà passato (associazioni, sequenze valutate, ecc.)
Puoi definire initialize_with
per tutte le factory includendolo nel bloccoFactoryGirl.define
:
FactoryGirl.define do initialize_with { new("Awesome first argument") }end
Quando usi initialize_with
, gli attributi accessibili dall’interno del blocco initialize_with
sono assegnati solo nel costruttore; questo equivale all’incirca al seguente codice:
FactoryGirl.define do factory :user do initialize_with { new(name) } name { 'value' } endendbuild(:user)# runsUser.new('value')
Questo previene l’assegnazione duplicata; nelle versioni di factory_girl precedenti alla 4.0, verrebbe eseguito questo:
Strategie personalizzate
Ci sono momenti in cui si potrebbe voler estendere il comportamento di factory_girl aggiungendo una strategia di costruzione personalizzata.
Le strategie definiscono due metodi: association
e result
. association
riceve un’istanza FactoryGirl::FactoryRunner
, sulla quale potete chiamarerun
, sovrascrivendo la strategia se volete. Il secondo metodo, result
, riceve un’istanza FactoryGirl::Evaluation
. Fornisce un modo per innescare i callback (con notify
), object
o hash
(per ottenere l’istanza del risultato o un ahash basato sugli attributi definiti nella factory), e create
, che esegue il callback to_create
definito nella factory.
Per capire come factory_girl usa le strategie internamente, è probabilmente più semplice visualizzare il sorgente per ciascuna delle quattro strategie di default.
Ecco un esempio di composizione di una strategia usandoFactoryGirl::Strategy::Create
per costruire una rappresentazione JSON del modello.
Per far sì che factory_girl riconosca la nuova strategia, puoi registrarla:
FactoryGirl.register_strategy(:json, JsonStrategy)
Questo ti permette di chiamare
FactoryGirl.json(:user)
Infine, puoi sovrascrivere le strategie di factory_girl se vuoi registrando un nuovo oggetto al posto delle strategie.
Callback personalizzati
I callback personalizzati possono essere definiti se stai usando strategie personalizzate:
Metodi personalizzati per persistere gli oggetti
Di default, la creazione di un record chiamerà save!
sull’istanza; poiché questo non è sempre ideale, si può sovrascrivere questo comportamento definendo to_create
sulla fabbrica:
factory :different_orm_model do to_create { |instance| instance.persist! }end
Per disabilitare del tutto il metodo di persistenza sulla creazione, si può skip_create
per quella fabbrica:
factory :user_without_database do skip_createend
Per sovrascrivere to_create
per tutte le fabbriche, definirlo all’interno del bloccoFactoryGirl.define
:
FactoryGirl.define do to_create { |instance| instance.persist! } factory :user do name "John Doe" endend
ActiveSupport Instrumentation
Al fine di tracciare quali fabbriche sono create (e con quale strategia di costruzione), ActiveSupport::Notifications
sono inclusi per fornire un modo per sottoscrivere le fabbriche in esecuzione. Un esempio potrebbe essere quello di tracciare le fabbriche basate su una soglia di tempo di esecuzione.
Un altro esempio potrebbe essere quello di tracciare tutte le fabbriche e come sono usate nella tua suite di test. Se state usando RSpec, è semplice come aggiungere unbefore(:suite)
e after(:suite)
:
Preloader Rails e RSpec
Quando si esegue RSpec con un preloader Rails come spring
o zeus
, è possibile incontrare un errore ActiveRecord::AssociationTypeMismatch
quando si crea una fabbrica con associazioni, come sotto:
L’errore si verifica durante l’esecuzione della suite di test:
Le due possibili soluzioni sono o eseguire la suite senza il preloader, o aggiungere FactoryGirl.reload
alla configurazione di RSpec, così:
RSpec.configure do |config| config.before(:suite) { FactoryGirl.reload }end
Usando senza Bundler
Se non stai usando Bundler, assicurati di avere la gemma installata e chiama:
require 'factory_girl'
Una volta richiesto, assumendo che tu abbia una struttura di directory di spec/factories
otest/factories
, tutto quello che dovrai fare è eseguire
FactoryGirl.find_definitions
Se stai usando una struttura di directory separata per i tuoi factory, puoi cambiare i percorsi dei file di definizione prima di cercare le definizioni:
FactoryGirl.definition_file_paths = %w(custom_factories_directory)FactoryGirl.find_definitions
Se non avete una directory separata di fabbriche e volete definirle in linea, anche questo è possibile:
require 'factory_girl'FactoryGirl.define do factory :user do name 'John Doe' date_of_birth { 21.years.ago } endend