- Opdatér din Gemfile
- Konfigurer din testsuite
- Test::Unit
- Cucumber
- Spinach
- Minitest
- Minitest::Spec
- minitest-rails
- Definering af fabrikker
- Brug af factories
- Dynamiske attributter
- Aliases
- Afhængige attributter
- Transient Attributes
- Metodenavn/reserverede ord-attributter
- Arv
- Associationer
- Sekvenser
- Traits
- Callbacks
- Modificering af fabrikker
- Bygning eller oprettelse af flere poster
- Linting Factories
- Brugerdefineret konstruktion
- Brugerdefinerede strategier
- Brugerdefinerede callbacks
- Brugerdefinerede metoder til at persistere objekter
- ActiveSupport Instrumentation
- Rails Preloaders og RSpec
- Kørsel uden Bundler
Opdatér din Gemfile
Hvis du bruger Rails, skal du ændre den krævede version af factory_girl_rails
:
gem "factory_girl_rails", "~> 4.0"
Hvis du ikke bruger Rails, skal du blot ændre den krævede version af factory_girl
:
gem "factory_girl", "~> 4.0"
export JRUBY_OPTS=--1.9
Når din Gemfile er opdateret, skal du opdatere din bundle.
Konfigurer din testsuite
Husk at kræve ovenstående fil i din rails_helper, da supportmappen ikke indlæses ivrigt
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
Hvis du ikke inkluderer FactoryGirl::Syntax::Methods
i din testsuite, skal alle factory_girl-metoder indledes med FactoryGirl
.
Definering af fabrikker
Hver fabrik har et navn og et sæt attributter. Navnet bruges som standard til at gætte objektets klasse, men det er muligt at angive det eksplicit:
Det anbefales stærkt, at du har en fabrik for hver klasse, som giver det enkleste sæt attributter, der er nødvendige for at oprette en instans af den pågældende klasse. Hvis du opretter ActiveRecord-objekter, betyder det, at du kun bør levere attributter, der er påkrævet gennem valideringer, og som ikke har standardindstillinger. Der kan oprettes andre fabrikker gennem arv for at dække almindelige scenarier for hver klasse.
Vil du forsøge at definere flere fabrikker med det samme navn, opstår der en fejl.
Factories kan defineres hvor som helst, men vil automatisk blive indlæst efter at have kaldt FactoryGirl.find_definitions
, hvis factories er defineret i filer på følgende steder:
test/factories.rbspec/factories.rbtest/factories/*.rbspec/factories/*.rb
Brug af factories
factory_girl understøtter flere forskellige opbygningsstrategier: build, create, attributes_for og build_stubbed:
Uanset hvilken strategi der anvendes, er det muligt at tilsidesætte de definerede attributter ved at overgive en hash:
# Build a User instance and override the first_name propertyuser = build(:user, first_name: "Joe")user.first_name# => "Joe"
Dynamiske attributter
De fleste fabriksattributter kan tilføjes ved hjælp af statiske værdier, der evalueres, når fabrikken defineres, men nogle attributter (såsom associationer og andreattributter, der skal genereres dynamisk) skal have tildelt værdier, hver gang en instans genereres. Disse “dynamiske” attributter kan tilføjes ved at overgive en blok i stedet for en parameter:
factory :user do # ... activation_code { User.generate_activation_code } date_of_birth { 21.years.ago }end
På grund af bloksyntaksen i Ruby kræver det to sæt krøllede parenteser at definere attributter som Hash
es (forserialized/JSON-kolonner, for eksempel):
factory :program do configuration { { auto_resolve: false, auto_define: true } }end
Aliases
factory_girl giver dig mulighed for at definere aliaser til eksisterende fabrikker for at gøre det lettere at genbruge dem. Dette kan være praktisk, når f.eks. dit Post-objekt har en author-attribut, der faktisk henviser til en instans af en User-klasse. Mens factory_girl normalt kan udlede fabriksnavnet fra associationsnavnet, vil den i dette tilfælde lede forgæves efter en author factory. Så alias din brugerfabrik, så den kan bruges under aliasnavne.
Afhængige attributter
Attributter kan baseres på værdierne af andre attributter ved hjælp af evaluatordet, der afgives til dynamiske attributblokke:
Transient Attributes
Der kan være tidspunkter, hvor din kode kan DRYed up ved at videregive transiente attributter til factories.
Statiske og dynamiske attributter kan oprettes som transiente attributter. Transientattributter vil blive ignoreret inden for attributes_for og vil ikke blive sat på modellen, selv om attributten eksisterer, eller du forsøger at tilsidesætte den.
Inden for factory_girls dynamiske attributter kan du få adgang til transientattributter, som du ville forvente. Hvis du skal have adgang til evaluatoren i et factory_girl callback, skal du erklære et andet blokargument (for evaluatoren) og få adgang til transiente attributter derfra.
Hvis dine attributter er i konflikt med eksisterende metoder eller reserverede ord, kan du definere dem med add_attribute
.
factory :dna do add_attribute(:sequence) { 'GATTACA' }endfactory :payment do add_attribute(:method) { 'paypal' }end
Arv
Du kan nemt oprette flere fabrikker for den samme klasse uden at gentage fælles attributter ved at indlejre fabrikker:
Du kan også tildele den overordnede eksplicit:
factory :post do title "A title"endfactory :approved_post, parent: :post do approved trueend
Som nævnt ovenfor er det god praksis at definere en grundlæggende fabrik for hver klasse med kun de attributter, der er nødvendige for at oprette den. Derefter skal du oprette mere specifikke fabrikker, der arver fra denne grundlæggende forælder. Fabriksdefinitioner er stadig kode, så hold dem DRY.
Associationer
Det er muligt at opstille associationer inden for fabrikker. Hvis fabriksnavnet er det samme som associationsnavnet, kan fabriksnavnet udelades.
factory :post do # ... authorend
Du kan også angive en anden fabrik eller overskrive attributter:
factory :post do # ... association :author, factory: :user, last_name: "Writely"end
Adfærden af associationsmetoden varierer afhængigt af den opbygningsstrategi, der anvendes for det overordnede objekt.
For ikke at gemme det tilknyttede objekt skal du angive strategi: :build i fabrikken:
Bemærk, at strategy: :build
-indstillingen skal overgives til et eksplicit kald til association
,og kan ikke bruges med implicitte associationer:
factory :post do # ... author strategy: :build # <<< this does *not* work; causes author_id to be nil
Generering af data til en has_many
-relation er lidt mere indviklet,afhængigt af den ønskede fleksibilitet, men her er et sikkert eksempel på generering af tilknyttede data.
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
Dette giver os mulighed for at gøre:
create(:user).posts.length # 0create(:user_with_posts).posts.length # 5create(:user_with_posts, posts_count: 15).posts.length # 15
Generering af data til et has_and_belongs_to_many
-forhold er meget lig ovenstående has_many
-forhold, med en lille ændring, du skal videregive et array af objekter til modellens pluraliserede attributnavn i stedet for et enkeltobjekt til den singulære version af attributnavnet.
Her er et eksempel med to modeller, der er relateret 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
Det giver os mulighed for at gøre:
Sekvenser
Unique værdier i et bestemt format (f.eks. e-mail-adresser) kan genereres ved hjælp af sekvenser. Sekvenser defineres ved at kalde sequence
i en definitionsblok, og værdier i en sekvens genereres ved at kaldegenerate
:
Sekvenser kan bruges i dynamiske attributter:
factory :invite do invitee { generate(:email) }end
Og som implicitte attributter:
factory :user do email # Same as `email { generate(:email) }`end
Og det er også muligt at definere en in-line-sekvens, der kun bruges i en bestemt fabrik:
factory :user do sequence(:email) { |n| "person#{n}@example.com" }end
Du kan også tilsidesætte den oprindelige værdi:
factory :user do sequence(:email, 1000) { |n| "person#{n}@example.com" }end
Og uden en blok vil værdien øge sig selv, startende ved sin oprindelige værdi:
factory :post do sequence(:position)end
Sekvenser kan også have aliaser. Sekvensaliaserne deler den samme tæller:
Definér aliaser og brug standardværdien (1) for tælleren
factory :user do sequence(:email, aliases: ) { |n| "person#{n}@example.com" }end
Sæt værdien:
factory :user do sequence(:email, 'a', aliases: ) { |n| "person#{n}@example.com" }end
Værdien skal blot understøtte metoden #next
. Her vil den næste værdi være ‘a’, derefter ‘b’ osv.
Traits
Traits giver dig mulighed for at gruppere attributter sammen og derefter anvende dem på enhver fabrik.
Traits kan bruges som attributter:
Traits, der definerer de samme attributter, vil ikke give anledning til AttributeDefinitionErrors;det trait, der senest definerer attributten, får forrang.
Du kan også tilsidesætte individuelle attributter, der er tildelt af et træk, i underklasser.
Traits kan også overføres som en liste af symboler, når du konstruerer en instans fra factory_girl.
Denne evne fungerer med build
, build_stubbed
, attributes_for
og create
.
>create_list
og build_list
-metoder understøttes også. Du skal blot huske at passe antallet af instanser, der skal oprettes/opbygges som anden parameter, som dokumenteret i afsnittet “Opbygning eller oprettelse af flere poster” i denne fil.
Traits kan også nemt bruges med associationer:
Når du bruger associationsnavne, der er forskellige fra fabrikkens:
Traits kan bruges inden for andre traits for at blande deres attributter.
factory :order do trait :completed do completed_at { 3.days.ago } end trait :refunded do completed refunded_at { 1.day.ago } endend
Endeligt kan traits acceptere transiente attributter.
Callbacks
factory_girl stiller fire callbacks til rådighed til at injicere noget kode:
Eksempler:
Bemærk, at du har en instans af brugeren i blokken. Dette kan være nyttigt.
Du kan også definere flere typer callbacks på den samme factory:
factory :user do after(:build) { |user| do_something_to(user) } after(:create) { |user| do_something_else_to(user) }end
Factories kan også definere et vilkårligt antal af den samme type callback. Disse callbacks vil blive udført i den rækkefølge, de er angivet:
factory :user do after(:create) { this_runs_first } after(:create) { then_this }end
Kaldes create
vil både after_build
og after_create
callbacks blive påkaldt.
Som standardattributter vil underordnede fabrikker også arve (og kan også definere) callbacks fra deres overordnede fabrik.
Flere callbacks kan tildeles til at køre en blok; dette er nyttigt, når man opbygger forskellige strategier, der kører den samme kode (da der ikke er nogen callbacks, der deles på tværs af alle strategier).
For at tilsidesætte callbacks for alle fabrikker skal du definere dem inden for blokkenFactoryGirl.define
:
Du kan også foretage callbacks, der er afhængige af Symbol#to_proc
:
Modificering af fabrikker
Hvis du får et sæt fabrikker (f.eks. fra en gem-udvikler), men ønsker at ændre dem, så de passer bedre ind i dit program, kan du modificere den pågældende fabrik i stedet for at oprette en underordnet fabrik og tilføje attributter der.
Hvis en gem giver dig en brugerfabrik:
FactoryGirl.define do factory :user do full_name "John Doe" sequence(:username) { |n| "user#{n}" } password "password" endend
I stedet for at oprette en child factory, der tilføjer yderligere attributter:
kan du ændre denne fabrik i stedet.
FactoryGirl.modify do factory :user do full_name "Jane Doe" date_of_birth { 21.years.ago } gender "Female" health 90 endend
Når du ændrer en fabrik, kan du ændre alle de attributter, du ønsker (bortset fra callbacks).
FactoryGirl.modify
skal kaldes uden for en FactoryGirl.define
blok, da den opererer anderledes på factories.
En advarsel: Du kan kun ændre factories (ikke sekvenser eller traits), og callbacks sammensættes stadig som de normalt ville gøre. Så hvis den fabrik, du ændrer, definerer en after(:create)
callback, vil du, hvis du definerer en after(:create)
, ikke tilsidesætte den, den vil bare blive kørt efter den første callback.
Bygning eller oprettelse af flere poster
I nogle tilfælde vil du oprette eller bygge flere instanser af en fabrik på én gang.
built_users = build_list(:user, 25)created_users = create_list(:user, 25)
Disse metoder opbygger eller opretter et bestemt antal fabrikker og returnerer dem som et array. for at indstille attributterne for hver af fabrikkerne kan du sende en hash, som du normalt ville gøre.
twenty_year_olds = build_list(:user, 25, date_of_birth: 20.years.ago)
build_stubbed_list
giver dig fuldt ud stubbed out-instanser:
stubbed_users = build_stubbed_list(:user, 25) # array of stubbed users
Der er også et sæt *_pair
-metoder til at oprette to poster ad gangen:
built_users = build_pair(:user) # array of two built userscreated_users = create_pair(:user) # array of two created users
Hvis du har brug for flere attribut-hashes, genererer attributes_for_list
dem:
users_attrs = attributes_for_list(:user, 25) # array of attribute hashes
Linting Factories
factory_girl giver mulighed for linting af kendte fabrikker:
FactoryGirl.lint
FactoryGirl.lint
opretter hver fabrik og fanger eventuelle undtagelser, der opstår under oprettelsesprocessen. FactoryGirl::InvalidFactoryError
bliver rejst med en liste over fabrikker (og tilsvarende undtagelser) for fabrikker, som ikke kunne oprettes.
Anbefalet brug af FactoryGirl.lint
er at køre dette i en opgave, før din testsuite udføres.Hvis du kører den i en before(:suite)
, vil det have en negativ indvirkning på dine testers ydeevne, når du kører enkelte tests.
Eksempel på Rake-opgave:
Når du kalder FactoryGirl.lint
, vil du sandsynligvis ønske at tømme databasen, da der højst sandsynligt vil blive oprettet poster. Ovenstående eksempel bruger database_cleaner-gem’en til at rydde databasen; sørg for at tilføje gem’en til din Gemfile under de relevante grupper.
Du kan lintes fabrikker selektivt ved kun at videregive fabrikker, du vil have lintet:
factories_to_lint = FactoryGirl.factories.reject do |factory| factory.name =~ /^old_/endFactoryGirl.lint factories_to_lint
Dette vil lintes alle fabrikker, der ikke er præfikseret med old_
.
Traits kan også lintes. Denne indstilling verificerer, at hver enkelt egenskab i en fabrik genererer et gyldigt objekt i sig selv.Dette slås til ved at overgive traits: true
til lint
-metoden:
FactoryGirl.lint traits: true
Dette kan også kombineres med andre argumenter:
FactoryGirl.lint factories_to_lint, traits: true
Du kan også angive den strategi, der anvendes til linting:
FactoryGirl.lint strategy: :build
Brugerdefineret konstruktion
Hvis du ønsker at bruge factory_girl til at konstruere et objekt, hvor nogle attributter er overgivet til initialize
, eller hvis du ønsker at gøre noget andet end blot at kalde new
på din byggeklasse, kan du tilsidesætte standardadfærden ved at definere initialize_with
på din factory. Eksempel:
Selv om factory_girl er skrevet til at fungere med ActiveRecord out of the box, kan den også fungere med enhver Ruby-klasse. For at opnå maksimal kompatibilitet med ActiveRecord opbygger standardinitialisatoren alle instanser ved at kalde new
på din byggeklasse uden nogen argumenter. Den kalder derefter attributskrivermetoder for at tildele alle attributværdierne. Mens det fungerer fint for ActiveRecord, fungerer det faktisk ikke for næsten alle andre Ruby-klasser.
Du kan overskrive initialisatoren for at:
- Byg ikke-ActiveRecord-objekter, der kræver argumenter til
initialize
- Brug en anden metode end
new
til at instantiere instansen - Gør skøre ting som at dekorere instansen, efter at den er bygget
Når du bruger initialize_with
, behøver du ikke at deklarere selve klassen, når du kalder new
; skal alle andre klassemetoder, du vil kalde, dog kaldes eksplicit på klassen.
For eksempel:
factory :user do name "John Doe" initialize_with { User.build_with_name(name) }end
Du kan også få adgang til alle offentlige attributter inden for initialize_with
-blokken ved at kalde attributes
:
factory :user do transient do 5 end name "John Doe" initialize_with { new(attributes) }end
Dette vil opbygge en hash af alle attributter, der skal overføres til new
. Det vil ikke omfatte transiente attributter, men alt andet, der er defineret i fabrikken, vil blive videregivet (associationer, evaluerede sekvenser osv.).)
Du kan definere initialize_with
for alle fabrikker ved at inkludere det i blokkenFactoryGirl.define
:
FactoryGirl.define do initialize_with { new("Awesome first argument") }end
Når du bruger initialize_with
, tildeles attributter, der tilgås fra initialize_with
blokken, kun i konstruktøren; det svarer til nogenlunde følgende kode:
FactoryGirl.define do factory :user do initialize_with { new(name) } name { 'value' } endendbuild(:user)# runsUser.new('value')
Dette forhindrer dobbelttildeling; i versioner af factory_girl før 4.0, ville den køre således:
Brugerdefinerede strategier
Der er tidspunkter, hvor du måske ønsker at udvide opførslen af factory_girl ved at tilføje en brugerdefineret opbygningsstrategi.
Strategier definerer to metoder: association
og result
. association
modtager en FactoryGirl::FactoryRunner
-instans, som du kan kalderun
og overstyre strategien, hvis du ønsker det. Den anden metode, result
,modtager en FactoryGirl::Evaluation
-instans. Den giver mulighed for at udløsecallbacks (med notify
), object
eller hash
(for at få resultatinstansen eller ahash baseret på de attributter, der er defineret i fabrikken) og create
, som udfører to_create
callback defineret på fabrikken.
For at forstå, hvordan factory_girl bruger strategier internt, er det nok lettest blot at se kilden for hver af de fire standardstrategier.
Her er et eksempel på sammensætning af en strategi ved hjælp afFactoryGirl::Strategy::Create
for at opbygge en JSON-repræsentation af din model.
For at factory_girl kan genkende den nye strategi, kan du registrere den:
FactoryGirl.register_strategy(:json, JsonStrategy)
Dette giver dig mulighed for at kalde
FactoryGirl.json(:user)
Endeligt kan du tilsidesætte factory_girls egne strategier, hvis du ønsker det, ved at registrere et nyt objekt i stedet for strategierne.
Brugerdefinerede callbacks
Der kan defineres brugerdefinerede callbacks, hvis du bruger bruger brugerdefinerede strategier:
Brugerdefinerede metoder til at persistere objekter
Som standard kalder oprettelsen af en post save!
på instansen.Da dette måske ikke altid er ideelt, kan du tilsidesætte denne adfærd ved at definereto_create
på fabrikken:
factory :different_orm_model do to_create { |instance| instance.persist! }end
For at deaktivere persistensmetoden helt og holdent på create kan du skip_create
for den pågældende fabrik:
factory :user_without_database do skip_createend
For at tilsidesætteto_create
for alle fabrikker skal du definere det i blokkenFactoryGirl.define
:
FactoryGirl.define do to_create { |instance| instance.persist! } factory :user do name "John Doe" endend
ActiveSupport Instrumentation
For at spore, hvilke fabrikker der oprettes (og med hvilken opbygningsstrategi), er ActiveSupport::Notifications
medtaget for at give en måde at abonnere på fabrikker, der køres. Et eksempel kunne være at spore fabrikker baseret på en tærskel for udførelsestid.
Et andet eksempel kunne være at spore alle fabrikker, og hvordan de bruges i hele testsuiten. Hvis du bruger RSpec, er det så enkelt som at tilføje enbefore(:suite)
ogafter(:suite)
:
Rails Preloaders og RSpec
Når du kører RSpec med en Rails Preloader som spring
eller zeus
, er det muligtat støde på en ActiveRecord::AssociationTypeMismatch
fejl, når du opretter en fabrik med associationer, som nedenfor:
Fejlen opstår under kørslen af testsuiten:
De to mulige løsninger er enten at køre suiten uden preloader ellerat tilføje FactoryGirl.reload
til RSpec-konfigurationen, som her:
RSpec.configure do |config| config.before(:suite) { FactoryGirl.reload }end
Kørsel uden Bundler
Hvis du ikke bruger Bundler, skal du sørge for at have perlen installeret og kalde:
require 'factory_girl'
Når det er påkrævet, forudsat at du har en mappestruktur på spec/factories
ellertest/factories
, skal du blot køre
FactoryGirl.find_definitions
Hvis du bruger en separat mappestruktur til dine fabrikker, kan du ændre stierne til definitionsfilerne, før du forsøger at finde definitioner:
FactoryGirl.definition_file_paths = %w(custom_factories_directory)FactoryGirl.find_definitions
Hvis du ikke har en separat mappe med fabrikker og gerne vil definere dem inline, så er det også muligt:
require 'factory_girl'FactoryGirl.define do factory :user do name 'John Doe' date_of_birth { 21.years.ago } endend