- Update Your Gemfile
- Configureer uw testsuite
- Test::Unit
- Cucumber
- Spinach
- Minitest
- Minitest::Spec
- minitest-rails
- Fabrieken definiëren
- Fabrieken gebruiken
- Dynamische attributen
- Aliassen
- Afhankelijke attributen
- Transient Attributen
- Methodennaam / Gereserveerde woord-attributen
- Erfenis
- Associaties
- Sequenties
- Traits
- Callbacks
- Factory’s wijzigen
- Building or Creating Multiple Records
- Linting Factories
- Custom Construction
- Aangepaste strategieën
- Custom Callbacks
- Aangepaste methoden om objecten te bewaren
- ActiveSupport Instrumentation
- Rails Preloaders en RSpec
- Gebruik zonder Bundler
Update Your Gemfile
Als je Rails gebruikt, moet je de vereiste versie van factory_girl_rails
:
gem "factory_girl_rails", "~> 4.0"
Als je Rails niet gebruikt, moet je alleen de vereiste versie van factory_girl
:
gem "factory_girl", "~> 4.0"
export JRUBY_OPTS=--1.9
Als je Gemfile is geupdate, wil je je bundel updaten.
Configureer uw testsuite
Houd in gedachten dat u het bovenstaande bestand in uw rails_helper moet hebben, omdat de ondersteuningsfolder niet eagerly geladen wordt
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
Als u FactoryGirl::Syntax::Methods
niet in uw testsuite opneemt, dan zullen alle factory_girl methodes moeten worden voorafgegaan door FactoryGirl
.
Fabrieken definiëren
Elke fabriek heeft een naam en een set attributen. De naam wordt standaard gebruikt om de klasse van het object te raden, maar het is mogelijk om deze expliciet op te geven:
Het is zeer aan te bevelen dat je voor elke klasse een fabriek hebt die de eenvoudigste set attributen levert die nodig is om een instantie van die klasse te maken. Als je ActiveRecord objecten maakt, betekent dit dat je alleen attributen moet leveren die nodig zijn door validaties en die geen defaults hebben. Andere fabrieken kunnen worden gemaakt door overerving om veel voorkomende scenario’s voor elke klasse te dekken.
Proberen om meerdere fabrieken met dezelfde naam te definiëren zal een foutmelding geven.
Fabrieken kunnen overal worden gedefinieerd, maar zullen automatisch worden geladen na het aanroepen van FactoryGirl.find_definitions
als fabrieken zijn gedefinieerd in bestanden op de volgende locaties:
test/factories.rbspec/factories.rbtest/factories/*.rbspec/factories/*.rb
Fabrieken gebruiken
factory_girl ondersteunt verschillende bouwstrategieën: build, create, attributes_for en build_stubbed:
Gelijk welke strategie wordt gebruikt, is het mogelijk om de gedefinieerde attributen te overschrijven door een hash door te geven:
# Build a User instance and override the first_name propertyuser = build(:user, first_name: "Joe")user.first_name# => "Joe"
Dynamische attributen
De meeste fabrieksattributen kunnen worden toegevoegd met behulp van statische waarden die worden geëvalueerd wanneer de fabriek wordt gedefinieerd, maar sommige attributen (zoals associaties en andere attributen die dynamisch moeten worden gegenereerd) hebben waarden nodig die iedere keer dat een instantie wordt gegenereerd worden toegekend. Deze “dynamische” attributen kunnen worden toegevoegd door een blok door te geven in plaats van een parameter:
factory :user do # ... activation_code { User.generate_activation_code } date_of_birth { 21.years.ago }end
Omwille van de bloksyntaxis in Ruby, vereist het definiëren van attributen als Hash
es (voor geserialiseerde/JSON kolommen, bijvoorbeeld) twee sets accolades:
factory :program do configuration { { auto_resolve: false, auto_define: true } }end
Aliassen
factory_girl staat je toe om aliassen te definiëren voor bestaande fabrieken om ze gemakkelijker te hergebruiken. Dit kan van pas komen wanneer, bijvoorbeeld, uw Post-object een auteurattribuut heeft dat eigenlijk verwijst naar een instantie van een gebruikersklasse. Terwijl normaal factory_girl de fabrieksnaam kan afleiden uit de associatienaam, zal het in dit geval tevergeefs zoeken naar een author factory. Dus, alias uw gebruiker fabriek, zodat het kan worden gebruikt onder alias namen.
Afhankelijke attributen
Attributen kunnen worden gebaseerd op de waarden van andere attributen met behulp van de evaluator die wordt geleverd aan dynamische attribuut blokken:
Transient Attributen
Er kunnen momenten zijn waarop uw code kan worden DRYed up door het doorgeven van transient attributen aan fabrieken.
Statische en dynamische attributen kunnen als transient attributen worden aangemaakt. Voorbijgaande attributen worden binnen attributes_for genegeerd en worden niet op het model ingesteld, zelfs als het attribuut bestaat of als je het probeert te overschrijven.
In de dynamische attributen van factory_girl heb je toegang tot voorbijgaande attributen zoals je zou verwachten. Als u toegang tot de evaluator in een factory_girl callback nodig hebt, moet u een tweede blokargument (voor de evaluator) declareren en van daaruit toegang tot de tijdelijke attributen krijgen.
Methodennaam / Gereserveerde woord-attributen
Als uw attributen in strijd zijn met bestaande methoden of gereserveerde woorden, kunt u ze definiëren met add_attribute
.
factory :dna do add_attribute(:sequence) { 'GATTACA' }endfactory :payment do add_attribute(:method) { 'paypal' }end
Erfenis
U kunt gemakkelijk meerdere fabrieken voor dezelfde klasse maken zonder gemeenschappelijke attributen te herhalen door fabrieken te nesten:
U kunt ook de ouder expliciet toewijzen:
factory :post do title "A title"endfactory :approved_post, parent: :post do approved trueend
Zoals hierboven vermeld, is het een goed gebruik om voor elke klasse een basisfabriek te definiëren met alleen de attributen die nodig zijn om de fabriek te maken. Maak dan meer specifieke fabrieken die erven van deze basis-ouder. Factory definities zijn nog steeds code, dus houd ze DRY.
Associaties
Het is mogelijk om associaties binnen factories op te zetten. Als de naam van de fabriek hetzelfde is als de naam van de associatie, kan de naam van de fabriek worden weggelaten.
factory :post do # ... authorend
U kunt ook een andere fabriek of override attributen opgeven:
factory :post do # ... association :author, factory: :user, last_name: "Writely"end
Het gedrag van de associatie methode varieert afhankelijk van de gebruikte build strategie voor het bovenliggende object.
Om het geassocieerde object niet op te slaan, specificeert u strategie: :build in de fabriek:
Merk op dat de strategy: :build
optie moet worden doorgegeven aan een expliciete oproep aan association
, en niet kan worden gebruikt met impliciete associaties:
factory :post do # ... author strategy: :build # <<< this does *not* work; causes author_id to be nil
Het genereren van gegevens voor een has_many
relatie is een beetje meer betrokken, afhankelijk van de gewenste hoeveelheid flexibiliteit, maar hier is een trefzeker voorbeeld van het genereren van geassocieerde gegevens.
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
Dit stelt ons in staat om te doen:
create(:user).posts.length # 0create(:user_with_posts).posts.length # 5create(:user_with_posts, posts_count: 15).posts.length # 15
Het genereren van gegevens voor een has_and_belongs_to_many
relatie is zeer vergelijkbaar met de bovenstaande has_many
relatie, met een kleine verandering, moet je een array van objecten door te geven aan het model van de meervoudige attribuut naam in plaats van een enkelobject aan de enkelvoudige versie van de attribuut naam.
Hier volgt een voorbeeld met twee modellen die zijn gerelateerd 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
Dit stelt ons in staat om te doen:
Sequenties
Unieke waarden in een specifiek formaat (bijvoorbeeld e-mailadressen) kunnen worden gegenereerd met behulp van sequenties. Sequenties worden gedefinieerd door sequence
in een definitieblok aan te roepen, en waarden in een sequentie worden gegenereerd doorgenerate
aan te roepen:
Sequenties kunnen worden gebruikt in dynamische attributen:
factory :invite do invitee { generate(:email) }end
Of als impliciete attributen:
factory :user do email # Same as `email { generate(:email) }`end
En het is ook mogelijk om een in-line sequentie te definiëren die alleen in een bepaalde fabriek wordt gebruikt:
factory :user do sequence(:email) { |n| "person#{n}@example.com" }end
Je kunt ook de beginwaarde overschrijven:
factory :user do sequence(:email, 1000) { |n| "person#{n}@example.com" }end
Zonder een blok, zal de waarde zichzelf verhogen, beginnend bij de beginwaarde:
factory :post do sequence(:position)end
Sequenties kunnen ook aliassen hebben. De aliassen van de sequenties delen dezelfde teller:
Definieer aliassen en gebruik standaardwaarde (1) voor de teller
factory :user do sequence(:email, aliases: ) { |n| "person#{n}@example.com" }end
Instellen van de waarde:
factory :user do sequence(:email, 'a', aliases: ) { |n| "person#{n}@example.com" }end
De waarde hoeft alleen maar de methode #next
te ondersteunen. Hier zal de volgende waarde ‘a’ zijn, dan ‘b’, enz.
Traits
Traits stellen u in staat attributen te groeperen en ze vervolgens op elke fabriek toe te passen.
Traits kunnen als attributen worden gebruikt:
Traits die dezelfde attributen definiëren, zullen geen AttributeDefinitionErrors oproepen; de trait die het attribuut het laatst definieert, krijgt voorrang.
U kunt ook individuele attributen die door een eigenschap in subklassen worden toegekend, overschrijven.
Eigenschappen kunnen ook als een lijst symbolen worden doorgegeven wanneer u een instantie van factory_girl construeert.
Deze mogelijkheid werkt met build
, build_stubbed
, attributes_for
, en create
.
create_list
en build_list
methoden worden ook ondersteund. Denk er alleen aan om het aantal te creëren/bouwen instanties als tweede parameter door te geven, zoals gedocumenteerd in de “Het bouwen of creëren van meerdere records” sectie van dit bestand.
Traits kunnen ook gemakkelijk worden gebruikt met associaties:
Wanneer u associatie namen gebruikt die anders zijn dan de fabriek:
Traits kunnen worden gebruikt binnen andere traits om hun attributen te mengen.
factory :order do trait :completed do completed_at { 3.days.ago } end trait :refunded do completed refunded_at { 1.day.ago } endend
Ten slotte kunnen traits transient attributen accepteren.
Callbacks
factory_girl stelt vier callbacks beschikbaar om wat code te injecteren:
Voorbeelden:
Merk op dat je een instantie van de gebruiker in het blok zult hebben. Dit kan handig zijn.
Je kunt ook meerdere soorten callbacks op dezelfde fabriek definiëren:
factory :user do after(:build) { |user| do_something_to(user) } after(:create) { |user| do_something_else_to(user) }end
Fabrieken kunnen ook een willekeurig aantal van hetzelfde soort callback definiëren. Deze callbacks zullen worden uitgevoerd in de volgorde waarin ze zijn gespecificeerd:
factory :user do after(:create) { this_runs_first } after(:create) { then_this }end
Het aanroepen van create
zal zowel after_build
als after_create
callbacks oproepen.
Ook zullen, net als standaard attributen, kind-fabrieken callbacks erven (en kunnen deze ook definiëren) van hun moeder-fabriek.
Meerdere callbacks kunnen worden toegewezen om een blok uit te voeren; dit is nuttig bij het bouwen van verschillende strategieën die dezelfde code uitvoeren (aangezien er geen callbacks zijn die over alle strategieën worden gedeeld).
Om callbacks voor alle factories te overschrijven, definieer ze binnen hetFactoryGirl.define
blok:
U kunt ook callbacks gebruiken die op Symbol#to_proc
vertrouwen:
Factory’s wijzigen
Als u een set van factories krijgt (bijvoorbeeld van een gem ontwikkelaar) maar ze wilt veranderen om beter in uw applicatie te passen, kunt u die factory wijzigen in plaats van een child factory te maken en daar attributen aan toe te voegen.
Als een gem je een User factory zou geven:
FactoryGirl.define do factory :user do full_name "John Doe" sequence(:username) { |n| "user#{n}" } password "password" endend
In plaats van een child factory te maken die extra attributen toevoegt:
Je zou die factory kunnen wijzigen.
FactoryGirl.modify do factory :user do full_name "Jane Doe" date_of_birth { 21.years.ago } gender "Female" health 90 endend
Bij het wijzigen van een factory, kun je alle attributen wijzigen die je wilt (afgezien van callbacks).
FactoryGirl.modify
moet buiten een FactoryGirl.define
blok worden aangeroepen, omdat het anders op fabrieken werkt.
Een caveat: je kunt alleen fabrieken wijzigen (niet sequenties of traits) en callbacks worden nog steeds samengesteld zoals ze normaal zouden doen. Dus, als de fabriek die u wijzigt definieert een after(:create)
callback, u het definiëren van een after(:create)
zal niet overschrijven, het zal gewoon worden uitgevoerd na de eerste callback.
Building or Creating Multiple Records
Soms, zult u willen maken of meerdere instanties van een fabriek in een keer te bouwen.
built_users = build_list(:user, 25)created_users = create_list(:user, 25)
Deze methoden bouwen of maken een specifiek aantal fabrieken en geven ze als een array terug. Om de attributen voor elk van de fabrieken in te stellen, kun je een hash doorgeven zoals je normaal zou doen.
twenty_year_olds = build_list(:user, 25, date_of_birth: 20.years.ago)
build_stubbed_list
geeft je volledig uitgesplitste instanties:
stubbed_users = build_stubbed_list(:user, 25) # array of stubbed users
Er is ook een set van *_pair
methoden om twee records tegelijk te maken:
built_users = build_pair(:user) # array of two built userscreated_users = create_pair(:user) # array of two created users
Als je meerdere attribuut hashes nodig hebt, genereert attributes_for_list
ze:
users_attrs = attributes_for_list(:user, 25) # array of attribute hashes
Linting Factories
factory_girl maakt het mogelijk om bekende factories te linting:
FactoryGirl.lint
FactoryGirl.lint
maakt elke fabriek en vangt alle uitzonderingen op die tijdens het creatieproces worden opgeworpen. FactoryGirl::InvalidFactoryError
krijgt een lijst met fabrieken (en bijbehorende uitzonderingen) voor fabrieken die niet konden worden gemaakt.
Aanbevolen gebruik van FactoryGirl.lint
is om dit in een taak uit te voeren voordat uw testsuite wordt uitgevoerd.Het uitvoeren in een before(:suite)
, zal een negatieve invloed hebben op de prestaties van uw tests bij het uitvoeren van enkele tests.
Voorbeeld Rake taak:
Na het aanroepen van FactoryGirl.lint
, zult u waarschijnlijk de database willen leegmaken, omdat er hoogstwaarschijnlijk records zullen worden aangemaakt. Het voorbeeld hierboven gebruikt de database_cleaner gem om de database op te schonen; zorg ervoor dat je de gem toevoegt aan je Gemfile onder de juiste groepen.
Je kunt fabrieken selectief linten door alleen fabrieken door te geven die je gelint wilt hebben:
factories_to_lint = FactoryGirl.factories.reject do |factory| factory.name =~ /^old_/endFactoryGirl.lint factories_to_lint
Dit zou alle fabrieken linten die niet voorafgegaan zijn door old_
.
Traits kunnen ook gelint worden. Deze optie controleert of elke eigenschap van een fabriek een geldig object op zichzelf genereert.Dit wordt aangezet door traits: true
door te geven aan de lint
methode:
FactoryGirl.lint traits: true
Dit kan ook gecombineerd worden met andere argumenten:
FactoryGirl.lint factories_to_lint, traits: true
U kunt ook de strategie opgeven die gebruikt wordt voor linting:
FactoryGirl.lint strategy: :build
Custom Construction
Als je factory_girl wilt gebruiken om een object te construeren waarbij sommige attributen worden doorgegeven aan initialize
of als je iets anders wilt doen dan simpelweg new
aanroepen op je build class, kun je het standaard gedrag overschrijven door initialize_with
te definiëren op je factory. Voorbeeld:
Hoewel factory_girl is geschreven om met ActiveRecord te werken, kan het ook met elke Ruby class werken. Voor maximale compatibiliteit met ActiveRecord, bouwt de standaard initializer alle instanties door new
aan te roepen op uw build class zonder argumenten. Het roept dan attribute writer methods aan om alle attribute waarden toe te kennen. Hoewel dat prima werkt voor ActiveRecord, werkt het eigenlijk niet voor bijna elke andere Ruby klasse.
Je kunt de initializer overschrijven om:
- Niet-ActiveRecord objecten te bouwen die argumenten nodig hebben om
initialize
- Een andere methode dan
new
te gebruiken om de instantie te instantiëren - Gekkere dingen te doen zoals de instantie decoreren nadat hij is gebouwd
Wanneer u initialize_with
gebruikt, hoeft u de klasse zelf niet te declareren wanneer u new
aanroept; maar alle andere methoden van de klasse die u wilt aanroepen, moeten expliciet op de klasse worden aangeroepen.
Bijv.:
factory :user do name "John Doe" initialize_with { User.build_with_name(name) }end
U kunt ook toegang krijgen tot alle publieke attributen binnen het initialize_with
blok door attributes
aan te roepen:
factory :user do transient do 5 end name "John Doe" initialize_with { new(attributes) }end
Dit zal een hash maken van alle attributen die aan new
moeten worden doorgegeven. Het zal geen transient attributen bevatten, maar al het andere dat in de fabriek is gedefinieerd zal worden doorgegeven (associaties, geëvalueerde sequenties, enz.)
U kunt initialize_with
voor alle fabrieken definiëren door het in hetFactoryGirl.define
blok op te nemen:
FactoryGirl.define do initialize_with { new("Awesome first argument") }end
Wanneer u initialize_with
gebruikt, worden attributen die vanuit het initialize_with
blok worden benaderd alleen in de constructor toegewezen; dit komt neer op ongeveer de volgende code:
FactoryGirl.define do factory :user do initialize_with { new(name) } name { 'value' } endendbuild(:user)# runsUser.new('value')
Dit voorkomt dubbele toewijzing; in versies van factory_girl voor 4.0, zou het zo lopen:
Aangepaste strategieën
Er zijn momenten waarop u het gedrag van factory_girl wilt uitbreiden door een aangepaste bouwstrategie toe te voegen.
Strategieën definiëren twee methoden: association
en result
. association
ontvangt een FactoryGirl::FactoryRunner
instantie, waarop urun
kunt aanroepen, de strategie overschrijvend als u wilt. De tweede methode, result
, ontvangt een FactoryGirl::Evaluation
instantie. Het biedt een manier om callbacks te triggeren (met notify
), object
of hash
(om de resultaat-instantie of ahash te krijgen op basis van de attributen die in de factory zijn gedefinieerd), en create
, die de to_create
callback uitvoert die op de factory is gedefinieerd.
Om te begrijpen hoe factory_girl strategieën intern gebruikt, is het waarschijnlijk het eenvoudigst om gewoon de bron voor elk van de vier standaard strategieën te bekijken.
Hier is een voorbeeld van het samenstellen van een strategie metFactoryGirl::Strategy::Create
om een JSON-weergave van uw model te bouwen.
Om factory_girl de nieuwe strategie te laten herkennen, kunt u deze registreren:
FactoryGirl.register_strategy(:json, JsonStrategy)
Dit stelt u in staat om
FactoryGirl.json(:user)
Ten slotte kunt u factory_girl’s eigen strategieën overschrijven als u dat wilt door een nieuw object te registreren in plaats van de strategieën.
Custom Callbacks
Custom callbacks kunnen worden gedefinieerd als u aangepaste strategieën gebruikt:
Aangepaste methoden om objecten te bewaren
Het aanmaken van een record roept standaard save!
op de instantie aan; omdat dit niet altijd ideaal kan zijn, kun je dat gedrag opheffen doorto_create
op de fabriek te definiëren:
factory :different_orm_model do to_create { |instance| instance.persist! }end
Om de persistentie methode helemaal uit te schakelen op het aanmaken, kun je skip_create
voor die fabriek:
factory :user_without_database do skip_createend
Om to_create
voor alle fabrieken te overschrijven, definieert u het in hetFactoryGirl.define
blok:
FactoryGirl.define do to_create { |instance| instance.persist! } factory :user do name "John Doe" endend
ActiveSupport Instrumentation
Om bij te houden welke fabrieken worden gemaakt (en met welke bouwstrategie), zijn ActiveSupport::Notifications
opgenomen om een manier te bieden om in te tekenen op factories die worden uitgevoerd. Een voorbeeld is het bijhouden van fabrieken op basis van een drempelwaarde voor de uitvoeringstijd.
Een ander voorbeeld is het bijhouden van alle fabrieken en hoe ze worden gebruikt in je testsuite. Als u RSpec gebruikt, is het zo eenvoudig als het toevoegen van eenbefore(:suite)
en after(:suite)
:
Rails Preloaders en RSpec
Wanneer u RSpec uitvoert met een Rails preloader zoals spring
of zeus
, is het mogelijk om een ActiveRecord::AssociationTypeMismatch
-fout tegen te komen bij het maken van een fabriek met associaties, zoals hieronder:
De fout treedt op tijdens het uitvoeren van de testsuite:
De twee mogelijke oplossingen zijn om ofwel de suite uit te voeren zonder de preloader, of om FactoryGirl.reload
toe te voegen aan de RSpec-configuratie, zoals dit:
RSpec.configure do |config| config.before(:suite) { FactoryGirl.reload }end
Gebruik zonder Bundler
Gebruik je geen Bundler, zorg er dan voor dat je de gem geïnstalleerd hebt en roep op:
require 'factory_girl'
Eenmaal nodig, ervan uitgaande dat je een directory structuur hebt van spec/factories
oftest/factories
, is het enige wat je hoeft te doen
FactoryGirl.find_definitions
Gebruik je een aparte directory structuur voor je fabrieken, dan kun je de definitie bestand paden veranderen voordat je probeert om definities te vinden:
FactoryGirl.definition_file_paths = %w(custom_factories_directory)FactoryGirl.find_definitions
Als je geen aparte directory met fabrieken hebt en je wilt ze inline definiëren, dan kan dat ook:
require 'factory_girl'FactoryGirl.define do factory :user do name 'John Doe' date_of_birth { 21.years.ago } endend