Share traits across different factories
Asked Answered
R

1

16

I have many models which can be authorable (have an author field) and/or tenancyable (have a tenant field). So, I wrote concerns for both of them.

The problem is in tests. I had used the shared_examples_for block to write tests for the concerns and include them into my model tests. Anyway, to do this, I have several traits and after blocks, for example:

after(:build) do |authorable|
  authorable.author = build(:user, tenant: authorable.tenant)
end

trait :no_author do
  after(:build) do |authorable|
    authorable.author = nil
  end
end

trait :no_tenant do
  tenant nil
end

This piece of code should be equal in the factories of all the models that are tenancyable and authorable.

I didn't found any way to do this. Is it possible?

Roorback answered 21/4, 2014 at 19:42 Comment(0)
C
26

Traits can be registered globally, so that they can be used in any other factory without using FactoryGirl's inheritance:

FactoryGirl.define do
  trait :no_author do
    after(:build) { |authorable| authorable.author = nil }
  end

  trait :no_tenant do
    tenant nil
  end

  factory :model do
    tenant  { build(:tenant) }
  end
end

You can then simply build your objects like this:

FactoryGirl.build(:model, :no_tenant)
FactoryGirl.build(:model, :no_author)

after callbacks can also be registered globally, but that would mean they are triggered for any object FactoryGirl creates, which may cause undesired side effects:

FactoryGirl.define do
  after(:build) do |authorable|
    authorable.author = build(:user, tenant: authorable.tenant)
  end

  factory :model do
    tenant  { build(:tenant) }
  end

  factory :other_model
end

FactoryGirl.build(:model)       # Happiness!
FactoryGirl.build(:other_model) # undefined method `tenant'

To avoid this, you can either wrap the callback in a trait, like you did in the :no_author trait, or you can use factory inheritance:

FactoryGirl.define do
  factory :tenancyable do
    trait :no_tenant do
      tenant nil
    end

    factory :authorable do
      after(:build) do |authorable|
        authorable.author = build(:user, tenant: authorable.tenant)
      end

      trait :no_author do
        after(:build) do |authorable|
          authorable.author = nil
        end
      end
    end
  end

  factory :model, parent: :authorable, class: 'Model' do
    tenant  { build(:tenant) }
  end

  factory :other_model
end

Note how the class for the model factory needs to be explicitly specified here to make this work. You can now build objects:

FactoryGirl.build(:model, :no_author) # Happiness!
FactoryGirl.build(:other_model)       # More Happiness!

With the second approach, the traits and callbacks are more contained. This may actually cause less unwanted surprises when you have a large codebase with many factories.

Caskey answered 14/5, 2014 at 8:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.