Skip to content

Instantly share code, notes, and snippets.

@jakl
Last active December 22, 2015 06:09
Show Gist options
  • Select an option

  • Save jakl/6428804 to your computer and use it in GitHub Desktop.

Select an option

Save jakl/6428804 to your computer and use it in GitHub Desktop.
Factory Girl in TTC

Factory Girl in TTC

Official Factory Girl Docs

spec/factories/project.rb

Here we demonstrate:

  • string sequences
  • many-to-one association
  • skipping acl validations
  • simple convenience trait
FactoryGirl.define do
 factory :project do
   sequence :name, 'a'
   sequence :long_name, 'a'
   user
   skip_acl_validations true

   trait :core do
     core true
   end
 end
end

Test this with rails console test

include FactoryGirl::Syntax::Methods
create(:project)
Project.first.attributes == {
 "id"=>269,
 "name"=>"a",
 "long_name"=>"a",
 "description"=>nil,
 "internal"=>true,
 "active"=>true,
 "created_at"=>Tue, 03 Sep 2013 20:16:38 UTC +00:00,
 "updated_at"=>Tue, 03 Sep 2013 20:16:38 UTC +00:00,
 "image_url"=>nil,
 "private"=>false,
 "force_output_rtl"=>false,
 "core"=>false,
 "export_config"=>nil,
 "user_id"=>2529
}

# Remember to cleanup your test database
Project.destroy_all
User.destroy_all # Automatically created from Project creation
Locale.destroy_all # Automatically created from User creation

spec/factories/user.rb

Here we demonstrate:

  • sequence of digits as a string
  • factory parameter, not saved as an attribute, using the ignore block
  • many-to-many association using after(:create)
  • attribute's value taken from block
  • Bonus Points: Is something missing? Can you tell me what should also be here?
FactoryGirl.define do
  factory :user do
    sequence :login, 'a'
    sequence :twitter_id, '1'

    # There's no validation requiring users to have locales
    #   however it's often assumed, so we'll require it here
    ignore do # Ignored attributes aren't set on the model
      locale { create :locale }
    end

    after(:create) do |user, ignored_attributes|
      locale = ignored_attributes.locale
      create(:translator_locale, user: user, locale: locale.code)
    end

    trait :blocked do
      blocked true
    end

    trait :moderator do
      after(:create) { |u| u.groups << Group.moderator }
    end

    trait :admin do
      after(:create) { |u| u.groups << Group.admin }
    end

    trait :engineer do
      after(:create) { |u| u.groups << Group.engineer }
    end
  end
end

spec/factories/priority_task.rb

Here we demonstrate:

  • complex convenience trait to build a typical production instance
  • use factory to create multiple instances at once
  • many-to-many association populated with a list
FactoryGirl.define do
  factory :priority_task do

    trait :with_basic_attributes do
      user
      sequence :description, 'a'
      sequence :jira_ticket, 'a'
      sequence :url, 'a'
      user_action_type
      locales { create_list :locale, 5 }
    end

    trait :active do
      active true
    end
  end
end

spec/factories/locale.rb

Find two things wrong with this factory.

FactoryGirl.define do
  factory :locale do
    sequence :code, 'a'
    sequence :english_name, 'a'
    sequence :local_name, 'a'

    trait :rtl do
      rtl true
    end

    trait :homepage do
      show_on_homepage true
    end

    factory :locale_on_homepage, traits: [:homepage]

    factory :locale_with_hashtag_widget do
      hashtag { hashtag_from_code }
      sequence :widget_id
    end
  end
end

Example Create Usage

Describe what this does? How does it work? What is added to the database?

locale = create(:locale, :rtl, english_name: 'esperanto')
user = create(:user, locale: locale)
create(:project, :core, long_name: 'esperanto', user: user)
create_list(:priority_task, 35, :active)

Example Build Attributes Usage

Don't want to save to the database right away? Build the attributes instead; maybe a controller wants to show off its saving skills!

project_attributes = build_attributes(:project, :core)
Project.create!(project_attributes)

Note: In a repl make sure to also require './spec/spec_helper.rb' to include the build_attributes method.

spec/spec_helper.rb

What is going on here? Why don't we call Factory Girl's attributes_for method directly?

# Steal attributes off of factory girl's build()
#   because attributes_for() doesn't include attributes for associations
# Source: http://stackoverflow.com/questions/10290286/factorygirl-why-does-attributes-for-omit-some-attributes
# Issue: https://github.com/thoughtbot/factory_girl/issues/316
#
# This is also an easy place to ditch the acl flag, which isn't an attribute
def build_attributes(*args)
  FactoryGirl.build(*args).attributes.delete_if do |k, v|
    v.nil? || k == 'skip_acl_validations'
  end
end

Style

"only provide attributes that are required through validations and that do not have defaults" -- docs

Traits add convenient optional properties, extending the base factory. Avoid using nested factories in favor of traits, which are composable and self-documenting.

Provide sequences in-line, even if using a block.

Quiz

What is the default sequence, with no params? i.e. sequence :my_attribute

When would be want a sequence with a block? i.e. sequence :my_attribute { |i| ... }

What would the factory for country.rb look like?

Over the horizon

Provide a :production trait for every factory so we can generate production-like data for local development, staging, and maybe smoke tests. This would replace dumping production mysql data into our local instances, and replace any plans of using static seed data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment