- Uppdatera din Gemfile
- Konfigurera din testföljd
- Test::Unit
- Cucumber
- Spinach
- Minitest
- Minitest::Spec
- minitest-rails
- Definition av fabriker
- Användning av fabriker
- Dynamiska attribut
- Alias
- Dependent Attributes
- Transient Attributes
- Metodnamn / Reserverade ordattribut
- Arvaderande
- Associationer
- Sekvenser
- Traits
- Callbacks
- Modifiera fabriker
- Bygg eller skapa flera poster
- Linting Factories
- Anpassad konstruktion
- Anpassade strategier
- Anpassade återkopplingar
- Anpassade metoder för att bevara objekt
- ActiveSupport Instrumentation
- Rails preloaders och RSpec
- Användning utan Bundler
Uppdatera din Gemfile
Om du använder Rails måste du ändra den erforderliga versionen av factory_girl_rails
:
gem "factory_girl_rails", "~> 4.0"
Om du inte använder Rails behöver du bara ändra den erforderliga versionen av factory_girl
:
gem "factory_girl", "~> 4.0"
export JRUBY_OPTS=--1.9
När din Gemfile är uppdaterad vill du uppdatera din bundle.
Konfigurera din testföljd
Kom ihåg att kräva ovanstående fil i din rails_helper eftersom stödmappen inte laddas 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
Om du inte inkluderar FactoryGirl::Syntax::Methods
i din testföljd måste alla factory_girl-metoder föregås av FactoryGirl
.
Definition av fabriker
Varje fabrik har ett namn och en uppsättning attribut. Namnet används för att gissa objektets klass som standard, men det är möjligt att ange det explicit:
Det rekommenderas starkt att du har en fabrik för varje klass som ger den enklaste uppsättningen attribut som behövs för att skapa en instans av den klassen. Om du skapar ActiveRecord-objekt innebär det att du endast bör tillhandahålla attribut som krävs genom valideringar och som inte har standardvärden. Andra fabriker kan skapas genom arv för att täcka vanliga scenarier för varje klass.
Försök att definiera flera fabriker med samma namn ger upphov till ett fel.
Faktorer kan definieras var som helst, men kommer automatiskt att laddas efter att ha anropat FactoryGirl.find_definitions
om fabrikerna är definierade i filer på följande platser:
test/factories.rbspec/factories.rbtest/factories/*.rbspec/factories/*.rb
Användning av fabriker
factory_girl har stöd för flera olika byggstrategier: build, create, attributes_for och build_stubbed:
Oavsett vilken strategi som används är det möjligt att åsidosätta de definierade attributen genom att skicka en hash:
# Build a User instance and override the first_name propertyuser = build(:user, first_name: "Joe")user.first_name# => "Joe"
Dynamiska attribut
De flesta fabriksattribut kan läggas till med hjälp av statiska värden som utvärderas när fabriken definieras, men vissa attribut (t.ex. associationer och andraattribut som måste genereras dynamiskt) behöver värden som tilldelas varje gång en instans skapas. Dessa ”dynamiska” attribut kan läggas till genom att skicka ablock istället för en parameter:
factory :user do # ... activation_code { User.generate_activation_code } date_of_birth { 21.years.ago }end
På grund av blocksyntaxen i Ruby krävs det två uppsättningar av svängda parenteser för att definiera attribut som Hash
es (till exempel för serialiserade/JSON-kolumner):
factory :program do configuration { { auto_resolve: false, auto_define: true } }end
Alias
factory_girl låter dig definiera aliaser till existerande factories för att göra dem lättare att återanvända. Detta kan vara praktiskt när till exempel ditt Post-objekt har ett author-attribut som i själva verket hänvisar till en instans av en User-klass. Medan factory_girl normalt sett kan härleda fabriksnamnet från associationsnamnet, kommer den i det här fallet att leta förgäves efter en author factory. Så aliasa din user factory så att den kan användas under aliasnamn.
Dependent Attributes
Attribut kan baseras på värdena för andra attribut med hjälp av den utvärderare som ges till dynamiska attributblock:
Transient Attributes
Det kan finnas tillfällen då koden kan göras torrare genom att skicka över transienta attribut till fabriker.
Statiska och dynamiska attribut kan skapas som transienta attribut. Transientattribut kommer att ignoreras inom attributes_for och kommer inte att ställas in på modellen, även om attributet existerar eller om du försöker åsidosätta det.
Inom factory_girls dynamiska attribut kan du få tillgång till transientattribut som du kan förvänta dig. Om du behöver komma åt utvärderaren i en factory_girl callback måste du deklarera ett andra blockargument (för utvärderaren) och komma åt transienta attribut därifrån.
Metodnamn / Reserverade ordattribut
Om dina attribut står i konflikt med befintliga metoder eller reserverade ord kan du definiera dem med add_attribute
.
factory :dna do add_attribute(:sequence) { 'GATTACA' }endfactory :payment do add_attribute(:method) { 'paypal' }end
Arvaderande
Du kan enkelt skapa flera fabriker för samma klass utan att upprepa gemensamma attribut genom att nästla fabriker:
Du kan också tilldela överordnaren explicit:
factory :post do title "A title"endfactory :approved_post, parent: :post do approved trueend
Som nämnts ovan är det en bra metod att definiera en grundläggande fabrik för varje klassmed endast de attribut som krävs för att skapa den. Skapa sedan mer specifika fabriker som ärver från denna grundläggande förälder. Fabriksdefinitioner är fortfarande kod, så håll dem DRY.
Associationer
Det är möjligt att skapa associationer inom fabriker. Om fabriksnamnet är detsamma som associationsnamnet kan fabriksnamnet utelämnas.
factory :post do # ... authorend
Du kan också ange en annan fabrik eller åsidosätta attribut:
factory :post do # ... association :author, factory: :user, last_name: "Writely"end
Associeringsmetodens beteende varierar beroende på vilken byggstrategi som används för det överordnade objektet.
För att inte spara det associerade objektet anger du strategy: :build i fabriken:
Observera att strategy: :build
-alternativet måste överföras till ett explicit anrop till association
,och kan inte användas med implicita associationer:
factory :post do # ... author strategy: :build # <<< this does *not* work; causes author_id to be nil
Generering av data för ett has_many
-förhållande är lite mer komplicerat,beroende på hur mycket flexibilitet som önskas,men här är ett säkert exempel på generering av associerade 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
Detta tillåter oss att göra:
create(:user).posts.length # 0create(:user_with_posts).posts.length # 5create(:user_with_posts, posts_count: 15).posts.length # 15
Generera data för en has_and_belongs_to_many
-relation är mycket likt ovanstående has_many
-relation, med en liten förändring, du måste skicka enarray av objekt till modellens pluraliserade attributnamn istället för ett enskiltobjekt till singularversionen av attributnamnet.
Här är ett exempel med två modeller som är relaterade 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
Detta gör att vi kan göra:
Sekvenser
Utomatiska värden i ett specifikt format (t.ex. e-postadresser) kan genereras med hjälp av sekvenser. Sekvenser definieras genom att anropa sequence
i ett definitionsblock, och värden i en sekvens genereras genom att anropagenerate
:
Sekvenser kan användas i dynamiska attribut:
factory :invite do invitee { generate(:email) }end
Och som implicita attribut:
factory :user do email # Same as `email { generate(:email) }`end
Det är också möjligt att definiera en inline-sekvens som bara används i en viss fabrik:
factory :user do sequence(:email) { |n| "person#{n}@example.com" }end
Det går också att åsidosätta det initiala värdet:
factory :user do sequence(:email, 1000) { |n| "person#{n}@example.com" }end
Och utan block kommer värdet att öka av sig självt, med utgångspunkt från det initiala värdet:
factory :post do sequence(:position)end
Sekvenser kan också ha alias. Sekvensaliaserna delar samma räknare:
Definiera aliaser och använd standardvärdet (1) för räknaren
factory :user do sequence(:email, aliases: ) { |n| "person#{n}@example.com" }end
Inställer värdet:
factory :user do sequence(:email, 'a', aliases: ) { |n| "person#{n}@example.com" }end
Värdet behöver bara ha stöd för #next
-metoden. Här blir nästa värde ”a”, sedan ”b” osv.
Traits
Traits gör det möjligt att gruppera attribut tillsammans och sedan tillämpa dem på vilken fabrik som helst.
Traits kan användas som attribut:
Traits som definierar samma attribut kommer inte att ge upphov till AttributeDefinitionErrors;den trait som definierar attributet senast får företräde.
Du kan också åsidosätta enskilda attribut som beviljas av ett trait i underklasser.
Traits kan också skickas in som en lista med symboler när du konstruerar en instans från factory_girl.
Denna förmåga fungerar med build
, build_stubbed
, attributes_for
och create
.
>create_list
och build_list
metoder har också stöd. Kom bara ihåg att passa antalet instanser som ska skapas/byggas som andra parameter, enligt dokumentationen i avsnittet ”Bygga eller skapa flera poster” i den här filen.
Traits kan lätt användas med associationer också:
När du använder associationsnamn som skiljer sig från fabriksnamnen:
Traits kan användas inom andra traits för att blanda in deras attribut.
factory :order do trait :completed do completed_at { 3.days.ago } end trait :refunded do completed refunded_at { 1.day.ago } endend
Finally, traits can accept transient attributes.
Callbacks
factory_girl gör fyra callbacks tillgängliga för att injicera lite kod:
Exempel:
Bemärk att du har en instans av användaren i blocket. Detta kan vara användbart.
Du kan också definiera flera typer av callbacks på samma factory:
factory :user do after(:build) { |user| do_something_to(user) } after(:create) { |user| do_something_else_to(user) }end
Factories kan också definiera ett valfritt antal av samma typ av callback. Dessa callbacks kommer att utföras i den ordning de anges:
factory :user do after(:create) { this_runs_first } after(:create) { then_this }end
Att anropa create
kommer att anropa både after_build
och after_create
callbacks.
I likhet med standardattribut kommer också underordnade fabriker att ärva (och kan också definiera) callbacks från sin överordnade fabrik.
Flera callbacks kan tilldelas för att köra ett block; detta är användbart när man bygger olika strategier som kör samma kod (eftersom det inte finns några callbacks som delas mellan alla strategier).
För att åsidosätta callbacks för alla fabriker definierar du dem inom blocketFactoryGirl.define
:
Du kan också kalla callbacks som förlitar sig på Symbol#to_proc
:
Modifiera fabriker
Om du får en uppsättning fabriker (t.ex. från en gem-utvecklare), men vill ändra dem så att de passar bättre in i ditt program, kan du modifiera den fabriken i stället för att skapa en underfabrik och lägga till attribut där.
Om en gem skulle ge dig en User factory:
FactoryGirl.define do factory :user do full_name "John Doe" sequence(:username) { |n| "user#{n}" } password "password" endend
Istället för att skapa en child factory som lägger till ytterligare attribut:
kan du modifiera den fabriken i stället.
FactoryGirl.modify do factory :user do full_name "Jane Doe" date_of_birth { 21.years.ago } gender "Female" health 90 endend
När du modifierar en factory kan du ändra alla attribut du vill (förutom callbacks).
FactoryGirl.modify
måste anropas utanför ett FactoryGirl.define
block eftersom det fungerar annorlunda på fabriker.
En varning: du kan bara ändra fabriker (inte sekvenser eller egenskaper) och callbacks består fortfarande som de normalt gör. Så om fabriken du ändrar definierar en after(:create)
callback, kommer du som definierar en after(:create)
inte att åsidosätta den, utan den kommer bara att köras efter den första callbacken.
Bygg eller skapa flera poster
Ibland vill du skapa eller bygga flera instanser av en fabrik på en gång.
built_users = build_list(:user, 25)created_users = create_list(:user, 25)
De här metoderna bygger eller skapar ett visst antal fabriker och returnerar dem som en array. för att ställa in attributen för var och en av fabrikerna kan du skicka in en hash som du normalt skulle göra.
twenty_year_olds = build_list(:user, 25, date_of_birth: 20.years.ago)
build_stubbed_list
ger dig helt stubbade instanser:
stubbed_users = build_stubbed_list(:user, 25) # array of stubbed users
Det finns också en uppsättning *_pair
metoder för att skapa två poster i taget:
built_users = build_pair(:user) # array of two built userscreated_users = create_pair(:user) # array of two created users
Om du behöver flera attribut-hashar genererar attributes_for_list
dem:
users_attrs = attributes_for_list(:user, 25) # array of attribute hashes
Linting Factories
factory_girl möjliggör linting av kända fabriker:
FactoryGirl.lint
FactoryGirl.lint
skapar varje fabrik och fångar upp eventuella undantag under skapandeprocessen. FactoryGirl::InvalidFactoryError
tas upp med en lista över fabriker (och motsvarande undantag) för fabriker som inte kunde skapas.
Rekommenderad användning av FactoryGirl.lint
är att köra detta i en aktivitet innan testsviten exekveras.Att köra den i en before(:suite)
,kommer att ha en negativ inverkan på prestandan hos dina tester när du kör enskilda tester.
Exempel på Rake-uppgift:
Efter att ha anropat FactoryGirl.lint
, vill du troligen tömma databasen, eftersom poster troligen kommer att skapas. I exemplet ovan används gemen database_cleaner för att rensa databasen; se till att lägga till gemen i din Gemfile under lämpliga grupper.
Du kan linta fabriker selektivt genom att bara skicka över fabriker som du vill ha lintade:
factories_to_lint = FactoryGirl.factories.reject do |factory| factory.name =~ /^old_/endFactoryGirl.lint factories_to_lint
Detta skulle linta alla fabriker som inte har prefixet old_
.
Traits kan också lintas. Detta alternativ kontrollerar att varje egenskap i en fabrik genererar ett giltigt objekt på egen hand.Detta aktiveras genom att skicka traits: true
till metoden lint
:
FactoryGirl.lint traits: true
Detta kan också kombineras med andra argument:
FactoryGirl.lint factories_to_lint, traits: true
Du kan också ange vilken strategi som används för linting:
FactoryGirl.lint strategy: :build
Anpassad konstruktion
Om du vill använda factory_girl för att konstruera ett objekt där vissa attribut överlämnats till initialize
eller om du vill göra något annat än att helt enkelt anropa new
på din byggklass, kan du åsidosätta standardbeteendet genom att definiera initialize_with
på din factory. Exempel:
Och även om factory_girl är skriven för att fungera med ActiveRecord, kan den också fungera med vilken Ruby-klass som helst. För maximal kompatibilitet med ActiveRecord bygger standardinitialiseraren alla instanser genom att anropa new
på din byggklass utan några argument. Den anropar sedan attributskrivermetoder för att tilldela allaattributvärden. Detta fungerar bra för ActiveRecord, men det fungerar faktiskt inte för nästan alla andra Ruby-klasser.
Du kan åsidosätta initialiseraren för att:
- Bygga icke-ActiveRecord-objekt som kräver argument till
initialize
- Använda en annan metod än
new
för att instantiera instansen - Göra galna saker som att dekorera instansen efter att den har byggts
När du använder initialize_with
behöver du inte deklarera klassen i sig själv när du kallar new
; Men alla andra klassmetoder som du vill anropa måste anropas explicit för klassen.
Till exempel:
factory :user do name "John Doe" initialize_with { User.build_with_name(name) }end
Du kan också få tillgång till alla offentliga attribut inom initialize_with
-blocket genom att anropa attributes
:
factory :user do transient do 5 end name "John Doe" initialize_with { new(attributes) }end
Detta kommer att bygga upp en hash av alla attribut som ska skickas till new
. Det kommer inte att inkludera övergående attribut, men allt annat som definieras i fabriken kommer att överföras (associationer, utvärderade sekvenser osv.).)
Du kan definiera initialize_with
för alla fabriker genom att inkludera det i blocket FactoryGirl.define
:
FactoryGirl.define do initialize_with { new("Awesome first argument") }end
När du använder initialize_with
, tilldelas attribut som nås från initialize_with
-blocket endast i konstruktören; detta motsvarar ungefär följande kod:
FactoryGirl.define do factory :user do initialize_with { new(name) } name { 'value' } endendbuild(:user)# runsUser.new('value')
Det här förhindrar dubbla tilldelningar; i versioner av factory_girl före 4.0 skulle den köra så här:
Anpassade strategier
Det finns tillfällen då du kanske vill utöka beteendet hos factory_girl genom att lägga till en anpassad byggstrategi.
Strategier definierar två metoder: association
och result
. association
tar emot en FactoryGirl::FactoryRunner
instans, på vilken du kan anropa run
och överordna strategin om du vill. Den andra metoden, result
,tar emot en FactoryGirl::Evaluation
-instans. Den ger ett sätt att utlösacallbacks (med notify
), object
eller hash
(för att få resultatinstansen eller ahash baserat på de attribut som definierats i fabriken), och create
, som verkställer den to_create
callback som definierats på fabriken.
För att förstå hur factory_girl använder strategier internt, är det troligen enklast att bara se källan för var och en av de fyra standardstrategierna.
Här är ett exempel på att komponera en strategi med hjälp avFactoryGirl::Strategy::Create
för att bygga en JSON-representation av din modell.
För att factory_girl ska känna igen den nya strategin kan du registrera den:
FactoryGirl.register_strategy(:json, JsonStrategy)
Detta gör att du kan anropa
FactoryGirl.json(:user)
Slutligt kan du åsidosätta factory_girls egna strategier om du vill genom att registrera ett nytt objekt i stället för strategierna.
Anpassade återkopplingar
Anpassade återkopplingar kan definieras om du använder anpassade strategier:
Anpassade metoder för att bevara objekt
Som standard kallas save!
på instansen när du skapar en post.Eftersom detta kanske inte alltid är idealiskt kan du åsidosätta det beteendet genom att definierato_create
på fabriken:
factory :different_orm_model do to_create { |instance| instance.persist! }end
För att inaktivera persistensmetoden helt och hållet på create kan du skip_create
för den fabriken:
factory :user_without_database do skip_createend
För att åsidosätta to_create
för alla fabriker definierar du det i blocketFactoryGirl.define
:
FactoryGirl.define do to_create { |instance| instance.persist! } factory :user do name "John Doe" endend
ActiveSupport Instrumentation
För att spåra vilka fabriker som skapas (och med vilken byggstrategi) ingår ActiveSupport::Notifications
för att tillhandahålla ett sätt att prenumerera på fabriker som körs. Ett exempel är att spåra fabriker baserat på ett tröskelvärde för exekveringstid.
Ett annat exempel är att spåra alla fabriker och hur de används i hela testsviten. Om du använder RSpec är det lika enkelt som att lägga till enbefore(:suite)
ochafter(:suite)
:
Rails preloaders och RSpec
När du kör RSpec med en Rails preloader som spring
eller zeus
är det möjligt att stöta på ett ActiveRecord::AssociationTypeMismatch
fel när du skapar en fabrik med associationer, som nedan:
Felet uppstår under körningen av testsviten:
De två möjliga lösningarna är att antingen köra sviten utan preloader eller att lägga till FactoryGirl.reload
i RSpec-konfigurationen, så här:
RSpec.configure do |config| config.before(:suite) { FactoryGirl.reload }end
Användning utan Bundler
Om du inte använder Bundler, se till att du har installerat gemen och anropar den:
require 'factory_girl'
När det krävs, förutsatt att du har en katalogstruktur spec/factories
eller test/factories
, är allt du behöver göra att köra
FactoryGirl.find_definitions
Om du använder en separat katalogstruktur för dina fabriker, kan du ändra sökvägarna till definitionsfilerna innan du försöker hitta definitionerna:
FactoryGirl.definition_file_paths = %w(custom_factories_directory)FactoryGirl.find_definitions
Om du inte har en separat katalog för fabriker och vill definiera dem inline är det också möjligt:
require 'factory_girl'FactoryGirl.define do factory :user do name 'John Doe' date_of_birth { 21.years.ago } endend