1
0
Fork 0
mirror of https://github.com/thoughtbot/factory_bot.git synced 2022-11-09 11:43:51 -05:00

Traits can be added to factories when the factory creates an instance

This allows for traits to be used with normal factories without having
to name every single factory that uses one (or many) traits.

So, instead of creating male_admin and female_admin factories:

    FactoryGirl.define do
      factory :user do
        trait(:admin)  { admin true }
        trait(:male)   { gender "Male" }
        trait(:female) { gender "Female" }

        factory :male_admin,   :traits => [:male, :admin]
        factory :female_admin, :traits => [:admin, :female]
      end
    end

    FactoryGirl.create(:male_admin)
    FactoryGirl.create(:female_admin)

You could just create a user with those traits assigned:

    FactoryGirl.create(:user, :admin, :male)
    FactoryGirl.create(:user, :admin, :female)

This can be combined with attribute overrides as expected.

    FactoryGirl.create(:user, :admin, :male,   :name => "John Doe")
    FactoryGirl.create(:user, :admin, :female, :name => "Jane Doe")
This commit is contained in:
Joshua Clayton 2011-11-18 09:25:49 -05:00
parent 845a76a595
commit 442ba18f14
5 changed files with 143 additions and 16 deletions

View file

@ -404,6 +404,26 @@ You can also override individual attributes granted by a trait in subclasses.
end
end
Traits can also be passed in as a list of symbols when you construct an instance from FactoryGirl.
factory :user do
name "Friendly User"
trait :male do
name "John Doe"
gender "Male"
end
trait :admin do
admin true
end
end
# creates an admin user with gender "Male" and name "Jon Snow"
FactoryGirl.create(:user, :admin, :male, :name => "Jon Snow")
This ability works with `build`, `build_stubbed`, `attributes_for`, and `create`.
Callbacks
---------

View file

@ -90,6 +90,12 @@ module FactoryGirl
@definition.compile
end
def with_traits(traits)
self.clone.tap do |factory_with_traits|
factory_with_traits.inherit_traits traits
end
end
protected
def class_name #:nodoc:
@ -132,6 +138,11 @@ module FactoryGirl
end
end
def initialize_copy(source)
super
@definition = @definition.clone
end
class Runner
def initialize(options = {})
@attributes = options[:attributes]

View file

@ -8,16 +8,17 @@ module FactoryGirl
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this set.
# * traits_and_overrides: +Array+
# [+*Array+] Traits to be applied
# [+Hash+] Attributes to overwrite for this set.
# * block:
# Yields the hash of attributes.
#
# Returns: +Hash+
# A set of attributes that can be used to build an instance of the class
# this factory generates.
def attributes_for(name, overrides = {}, &block)
FactoryGirl.factory_by_name(name).run(Proxy::AttributesFor, overrides, &block)
def attributes_for(name, *traits_and_overrides, &block)
run_factory_girl_proxy(name, traits_and_overrides, Proxy::AttributesFor, &block)
end
# Generates and returns an instance from this factory. Attributes can be
@ -26,16 +27,17 @@ module FactoryGirl
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
# * traits_and_overrides: +Array+
# [+*Array+] Traits to be applied
# [+Hash+] Attributes to overwrite for this instance.
# * block:
# Yields the built instance.
#
# Returns: +Object+
# An instance of the class this factory generates, with generated attributes
# assigned.
def build(name, overrides = {}, &block)
FactoryGirl.factory_by_name(name).run(Proxy::Build, overrides, &block)
def build(name, *traits_and_overrides, &block)
run_factory_girl_proxy(name, traits_and_overrides, Proxy::Build, &block)
end
# Generates, saves, and returns an instance from this factory. Attributes can
@ -48,16 +50,17 @@ module FactoryGirl
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
# * traits_and_overrides: +Array+
# [+*Array+] Traits to be applied
# [+Hash+] Attributes to overwrite for this instance.
# * block:
# Yields the created instance.
#
# Returns: +Object+
# A saved instance of the class this factory generates, with generated
# attributes assigned.
def create(name, overrides = {}, &block)
FactoryGirl.factory_by_name(name).run(Proxy::Create, overrides, &block)
def create(name, *traits_and_overrides, &block)
run_factory_girl_proxy(name, traits_and_overrides, Proxy::Create, &block)
end
# Generates and returns an object with all attributes from this factory
@ -67,15 +70,16 @@ module FactoryGirl
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
# * traits_and_overrides: +Array+
# [+*Array+] Traits to be applied
# [+Hash+] Attributes to overwrite for this instance.
# * block
# Yields the stubbed object.
#
# Returns: +Object+
# An object with generated attributes stubbed out.
def build_stubbed(name, overrides = {}, &block)
FactoryGirl.factory_by_name(name).run(Proxy::Stub, overrides, &block)
def build_stubbed(name, *traits_and_overrides, &block)
run_factory_girl_proxy(name, traits_and_overrides, Proxy::Stub, &block)
end
# Builds and returns multiple instances from this factory as an array. Attributes can be
@ -125,6 +129,24 @@ module FactoryGirl
def generate(name)
FactoryGirl.sequence_by_name(name).next
end
private
def run_factory_girl_proxy(name, traits_and_overrides, proxy, &block)
overrides = if traits_and_overrides.last.respond_to?(:has_key?)
traits_and_overrides.pop
else
{}
end
factory = FactoryGirl.factory_by_name(name)
if traits_and_overrides.any?
factory = factory.with_traits(traits_and_overrides)
end
factory.run(proxy, overrides, &block)
end
end
end
end

View file

@ -200,3 +200,57 @@ describe "traits with callbacks" do
its(:name) { should == "JOHN" }
end
end
describe "traits added via proxy" do
before do
define_model("User", :name => :string, :admin => :boolean)
FactoryGirl.define do
factory :user do
name "John"
trait :admin do
admin true
end
trait :great do
after_create {|user| user.name.upcase! }
end
end
end
end
context "adding traits in create" do
subject { FactoryGirl.create(:user, :admin, :great, :name => "Joe") }
its(:admin) { should be_true }
its(:name) { should == "JOE" }
it "doesn't modify the user factory" do
subject
FactoryGirl.create(:user).should_not be_admin
FactoryGirl.create(:user).name.should == "John"
end
end
context "adding traits in build" do
subject { FactoryGirl.build(:user, :admin, :great, :name => "Joe") }
its(:admin) { should be_true }
its(:name) { should == "Joe" }
end
context "adding traits in attributes_for" do
subject { FactoryGirl.attributes_for(:user, :admin, :great) }
its([:admin]) { should be_true }
its([:name]) { should == "John" }
end
context "adding traits in build_stubbed" do
subject { FactoryGirl.build_stubbed(:user, :admin, :great, :name => "Jack") }
its(:admin) { should be_true }
its(:name) { should == "Jack" }
end
end

View file

@ -286,3 +286,23 @@ describe FactoryGirl::Factory, "running a factory" do
block_run.should == "changed"
end
end
describe FactoryGirl::Factory, "#with_traits" do
subject { FactoryGirl::Factory.new(:user) }
let(:admin_trait) { FactoryGirl::Trait.new(:admin) }
let(:female_trait) { FactoryGirl::Trait.new(:female) }
before do
FactoryGirl.register_trait(admin_trait)
FactoryGirl.register_trait(female_trait)
end
it "returns a factory with the correct traits" do
subject.with_traits([:admin, :female]).traits.should =~ [admin_trait, female_trait]
end
it "doesn't modify the original factory's traits" do
subject.with_traits([:admin, :female])
subject.traits.should be_empty
end
end