Philly.rb meetup
Rails testing:
factories or
fixtures?
factories or
Michael Toppa
March 11, 2014
@mtoppa
Why use factories or fixtures?
❖ Factories and fixtures can simplify otherwise repetitive and
complex data setup for tests...
Comparisons
❖ Tests with no factories or fixtures
❖ Tests with fixtures
❖ Tests with factories, using FactoryGirl
❖ We’ll ...
Partial data model for our
examples
CandidateCandidate
(politician)(politician)
RaceRaceCampaignCampaign
OfficeOffice
Curr...
Our test case
Tests with no factories or
fixtures
Ok for simple cases
# spec/models/candidate_spec.rb
describe Candidate do
describe "#calculate_completeness" do
it "return...
Not so good for complex
cases# spec/models/race_spec.rb
describe Race do
describe '#inaugurate!' do
it 'makes Mike Toppa P...
Tests with fixtures
The fixture files
# spec/fixtures/candidates.yml
mike_toppa:
id: 1
name: Mike Toppa
gender: M
[etc…]
# spec/fixtures/offic...
The test file
# spec/models/race_spec.rb
describe Race do
describe '#inaugurate!' do
it 'makes Mike Toppa President of the...
Pros
❖ For simple scenarios, re-usable across tests
❖ Easy to generate from live data
❖ Fixture files are easy to read
❖ T...
Cons
❖ Brittle
❖ If you add required fields to a model, you have to update
all its fixtures
❖ Fixture files are an externa...
Tests with factories,
using FactoryGirl
using FactoryGirl
The factory files
# spec/factories/candidates.rb
FactoryGirl.define do
factory :candidate do
name 'Jane Doe'
gender 'F'
[e...
The test file
# spec/models/race_spec.rb
describe Race do
describe '#inaugurate!' do
it 'makes Mike Toppa President of the...
But wait, there’s more…
Randomized values with the
Faker gem
# spec/factories/candidates.rb
FactoryGirl.define do
factory :candidate do
name { Fak...
Debate on randomized values
❖ Argument for:
❖ Having a wide variety of values, and combinations of values,
in your tests c...
Instantiation options
❖ create: saves your object to the database, and saves
any associated objects to the database
❖ buil...
Instantiation options
❖ Use build_stubbed whenever possible - your tests
will be faster!
❖ You will need to use create or ...
For frequent scenarios: child
factories
# spec/factories/offices.rb
FactoryGirl.define do
factory :office do
title 'Mayor'...
Child factories using other child
factories
# spec/factories/offices.rb
FactoryGirl.define do
factory :office do
area
titl...
For frequent scenarios: traits
# spec/factories/candidates.rb
FactoryGirl.define do
factory :candidate do
name 'Jane Doe'
...
Don’t overuse child factories
and traits - leads to brittleness
Factories address shortcomings of
fixtures
❖ Not as brittle
❖ Factory won’t break if you add a new required field to a mod...
Upcoming SlideShare
Loading in...5
×

Rails testing: factories or fixtures?

3,022

Published on

A comparison of factories and fixtures for providing data in Rspec tests in Ruby on Rails, with a particular focus on FactoryGirl

Published in: Technology
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
3,022
On Slideshare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
7
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide
  • Introductions to using tools usually have overly-simple examples. We’re going to use more realistic examples, as you can only get a feel for the differences between factories and fixtures when dealing with more complex scenarios.
  • The method we’ll examine is for inaugurating a new office holder, and we’ll make me President of the United States
  • Fast, simple, clear (not hitting the database)
  • Specifying all the attributes is cumbersome
    and when reading the test, distracts from understanding the purpose of the test
    Brittle: if add a required field to a model, you have to update all the tests using that model
    Hard to reuse
  • For each model’s fixture file, you can have multiple records defined
    updated_at and created_at are set automatically with the current time, or you can specify them
  • A simple factory file can look a lot like a fixture file…
  • … but a key difference is that you can override values and specify associations when writing the test
  • Rails testing: factories or fixtures?

    1. 1. Philly.rb meetup Rails testing: factories or fixtures? factories or Michael Toppa March 11, 2014 @mtoppa
    2. 2. Why use factories or fixtures? ❖ Factories and fixtures can simplify otherwise repetitive and complex data setup for tests ❖ Your need for them in unit tests will be light if you do TDD with loosely-coupled code ❖ But they are vital for unit-“ish” testing if you’re working with tightly coupled code ❖ e.g. most Rails apps, and the examples in this presentation ❖ They are great for integration testing
    3. 3. Comparisons ❖ Tests with no factories or fixtures ❖ Tests with fixtures ❖ Tests with factories, using FactoryGirl ❖ We’ll test the same method in each case, so you can clearly see the differences
    4. 4. Partial data model for our examples CandidateCandidate (politician)(politician) RaceRaceCampaignCampaign OfficeOffice CurrentCurrent HoldersHolders Winning Campaign
    5. 5. Our test case
    6. 6. Tests with no factories or fixtures
    7. 7. Ok for simple cases # spec/models/candidate_spec.rb describe Candidate do describe "#calculate_completeness" do it "returns 0 when no fields are filled out" do toppa = Candidate.new toppa.calculate_completeness.should eq(0.0) end it "returns a ratio of filled-out fields to total fields" do toppa = Candidate.new toppa.name = "Mike Toppa" toppa.facebook_url = "https://facebook/ElJefe" toppa.wikipedia_url = "http://en.wikipedia.org/wiki/Mike_Toppa" toppa.calculate_completeness.should eq(0.2) # 3 / 15 = 0.2 end end
    8. 8. Not so good for complex cases# spec/models/race_spec.rb describe Race do describe '#inaugurate!' do it 'makes Mike Toppa President of the United States' do toppa = Candidate.create( name: 'Mike Toppa', [and all required attributes] ) president = Office.create( title: 'President of the United States', [and all required attributes] ) presidential_race = Race.create([president + all req attrs]) toppa_campaign = Campaign.create([toppa + presidential_race + all req attrs]) presidential_race.winning_campaign = toppa_campaign presidential_race.inaugurate! president.current_holders.first.should == toppa end end end
    9. 9. Tests with fixtures
    10. 10. The fixture files # spec/fixtures/candidates.yml mike_toppa: id: 1 name: Mike Toppa gender: M [etc…] # spec/fixtures/office.yml president: id: 1 title: President of the United States level: N [etc…] # spec/fixtures/race.yml president_race_2012: id: 1 office: president election_day: 10/4/2012 [etc…] # spec/fixtures/campaign.yml toppa_us_president_campaign_2012: id: 1 race: president_race_2012 candidate: mike_toppa fec_id: XYZ
    11. 11. The test file # spec/models/race_spec.rb describe Race do describe '#inaugurate!' do it 'makes Mike Toppa President of the United States' do toppa = candidates(:mike_toppa) president = offices(:president) presidential_race = races(:us_president_race_2012) toppa_campaign = campaigns(:toppa_us_president_campaign_2012) presidential_race.winning_campaign = toppa_campaign presidential_race.inaugurate! president.current_holders.first.should == toppa end end end
    12. 12. Pros ❖ For simple scenarios, re-usable across tests ❖ Easy to generate from live data ❖ Fixture files are easy to read ❖ Tests are fairly fast ❖ Records are inserted based on the fixture files, bypassing ActiveRecord
    13. 13. Cons ❖ Brittle ❖ If you add required fields to a model, you have to update all its fixtures ❖ Fixture files are an external dependency ❖ Fixtures are organized by model - you need to keep track of their relationships with test scenarios ❖ Not dynamic - you get the same values every time ❖ (this may or may not be ok)
    14. 14. Tests with factories, using FactoryGirl using FactoryGirl
    15. 15. The factory files # spec/factories/candidates.rb FactoryGirl.define do factory :candidate do name 'Jane Doe' gender 'F' [etc…] # spec/factories/offices.rb FactoryGirl.define do factory :office do title 'Senator' level 'N' [etc…] # spec/factories/races.rb FactoryGirl.define do factory :race do office election_day '11/04/2014'.to_datetime [etc…] # spec/factories/campaigns.rb FactoryGirl.define do factory :campaign do candidate race fec_id 'XYZ'
    16. 16. The test file # spec/models/race_spec.rb describe Race do describe '#inaugurate!' do it 'makes Mike Toppa President of the United States' do toppa = create :candidate, name: 'Mike Toppa' president = create( :office, title: 'President of the United States' ) presidential_race = create :race, office: president toppa_campaign = create( :campaign, candidate: toppa, race: presidential_race ) presidential_race.winning_campaign = toppa_campaign presidential_race.inaugurate! president.current_holders.first.should == toppa end end end
    17. 17. But wait, there’s more…
    18. 18. Randomized values with the Faker gem # spec/factories/candidates.rb FactoryGirl.define do factory :candidate do name { Faker::Name.name } wikipedia_url { Faker::Internet.url } short_bio { Faker::Lorem.paragraph } phone { Faker::PhoneNumber.phone_number} address_1 { Faker::Address.street_address } address_2 { Faker::Address.secondary_address } city { Faker::Address.city } state { Faker::Address.state } zip { Faker::Address.zip_code } # and other kinds of randomized values gender { |n| %w[M F].sample } sequence(:pvs_id) azavea_updated_at { Time.at(rand * Time.now.to_i) } [etc…]
    19. 19. Debate on randomized values ❖ Argument for: ❖ Having a wide variety of values, and combinations of values, in your tests can expose bugs you might otherwise miss ❖ Argument against: ❖ In a test using many different model instances, failures can be difficult to reproduce and debug ❖ If you’re counting on randomized values to find bugs, your design process may not be robust
    20. 20. Instantiation options ❖ create: saves your object to the database, and saves any associated objects to the database ❖ build: builds your object in memory only, but still saves any associated objects to the database ❖ build_stubbed: builds your object in memory only, as well as any associated objects
    21. 21. Instantiation options ❖ Use build_stubbed whenever possible - your tests will be faster! ❖ You will need to use create or build for integration testing ❖ …and if you’re stuck with tightly coupled Rails code
    22. 22. For frequent scenarios: child factories # spec/factories/offices.rb FactoryGirl.define do factory :office do title 'Mayor' level 'L' [etc…] factory :office_president do status 'A' title 'President of the United States' level 'N' [etc…] end end end # spec/models/race_spec.rb describe Race do describe '#inaugurate!' do it 'makes Mike Toppa President of the United States' do president = create :office_president [etc…] end end end
    23. 23. Child factories using other child factories # spec/factories/offices.rb FactoryGirl.define do factory :office do area title 'Mayor' level 'L' [etc…] factory :office_house do association :area, factory: :congressional_district status 'A' level 'N' type_code 'H' [etc…] end end end
    24. 24. For frequent scenarios: traits # spec/factories/candidates.rb FactoryGirl.define do factory :candidate do name 'Jane Doe' gender 'F' [etc…] end trait :with_office_house do after :create do |candidate| office = create :office_house create :current_office_holder, :office => office, :candidate => candidate end end end # spec/models/race_spec.rb describe Race do describe '#inaugurate!' do it 'makes Mike Toppa President of the United States' do toppa = create: candidate, :with_office_house [etc…] end end end
    25. 25. Don’t overuse child factories and traits - leads to brittleness
    26. 26. Factories address shortcomings of fixtures ❖ Not as brittle ❖ Factory won’t break if you add a new required field to a model ❖ You don’t need to maintain complex scenarios spread out across fixture files ❖ Lessened external dependency, more flexibility ❖ You can define the attributes important to the test in the test code itself ❖ When using build_stubbed your tests will be faster ❖ But fixtures are faster when inserting records
    1. A particular slide catching your eye?

      Clipping is a handy way to collect important slides you want to go back to later.

    ×