Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Rails testing: factories or fixtures?

7,772 views

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
  • Be the first to comment

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

×