Remove Duplication with FactoryGirl's Traits

FactoryGirl’s traits are an
outstanding way to DRY up your tests and factories by naming groups of
attributes, callbacks, and associations in one concise area. Imagine defining
factories but without the attributes backed by a specific object. Here’s a
basic example of a factory with two traits:

FactoryGirl.define do
factory :todo_item do
name 'Pick up a gallon of milk'
trait :completed do
complete true
end
trait :not_completed do
complete false
end
end
end

This would allow you to declare a complete or incomplete todo item very
easily:

create(:todo_item, :completed)
create(:todo_item, :not_completed)

Pretty handy, eh? The other way to go about this would be to have different
factories altogether for complete and incomplete:

FactoryGirl.define do
factory :todo_item, aliases: [:incomplete_todo_item] do
name 'Pick up a gallon of milk'
complete false
factory :complete_todo_item do
complete true
end
end
end

This may work just fine, but the problem I have with this is that any other
permutations now have to be duplicated across both paths (complete and
incomplete). What happens when todos get comments?

FactoryGirl.define do
factory :todo_item, aliases: [:incomplete_todo_item] do
name 'Pick up a gallon of milk'
complete false
factory :incomplete_todo_item_with_comments do
after(:create) do |instance|
create_list :comment, 2, todo_item: instance
end
end
factory :complete_todo_item do
complete true
factory :complete_todo_item_with_comments do
after(:create) do |instance|
create_list :comment, 2, todo_item: instance
end
end
end
end
end

This introduces duplication of creating comments because both the incomplete and
complete todo items support comment creation. The alternative keeps the
components of being complete/incomplete and having comments separate.

FactoryGirl.define do
factory :todo_item do
name 'Pick up a gallon of milk'
trait :completed do
complete true
end
trait :not_completed do
complete false
end
trait :with_comments do
after(:create) do |instance|
create_list :comment, 2, todo_item: instance
end
end
end
end

Traits make your factories much more flexible, allowing you to add groups of
attributes where needed.

Additionally, you’re dealing with traits as concepts. This means that the
mechanics of achieving some sort of desired state are encapsulated. If the
logic of your application changed so that an item being complete was not a
boolean but rather a timestamp (for when the item was done), it’d be as
simple as changing the trait:

FactoryGirl.define do
factory :todo_item do
name 'Pick up a gallon of milk'
trait :completed do
# complete true
completed_at { Time.now }
end
trait :not_completed do
# complete false
completed_at nil
end
end
end

Traits are a great way to add individual pieces of state to a factory without
having to implement a massive hierarchy. Go
forth
and use traits!

Hound
automatically reviews Ruby, JavaScript, CoffeeScript, and SCSS code
in your GitHub pull requests and comments on style violations.
It is free for open source repos and $12/month per private repo.