CafeHey
CafeHey

Reputation: 5800

Rspec / FactoryBot factories are not running after_initialize when createing a new instance in my rails app

I have some models that use an after_initialize hook like so to set a load of default attributes and other things:

after_initialize do |some_model|
  initializer(some_model)
end

def initializer(some_model)
  #... loads of pre-processing
end

This all runs great in development and production, however in my FactoryBot and rspec tests this hook isn't running. I have a factory that looks like this:

FactoryBot.define do
  factory :some_model do
    some_number { 1 }
    is_it_something { false }
    account { create(:user).accounts.first }
  end
end

But my tests are looking like this:

some_model = create(:some_model)
some_model.initializer(some_model)

The issue is I'm having to call the initializer seperatly after creating the FactoryBot model. This is causing all sorts of issues because the create() triggers the create validations, and they fail because the initializer isn't ran.

How can I get FactoryBot to run after_initialize when creating a new instance?

Thanks.

(I might have got some terminology wrong here, so please correct me if I have.)

Upvotes: 2

Views: 1829

Answers (2)

arieljuod
arieljuod

Reputation: 15838

You could use the after(:build) callback of the factory to call the initializer since factorybot sets the attributes after initialization.

FactoryBot.define do
  factory :some_model do
    some_number { 1 }
    is_it_something { false }
    account { create(:user).accounts.first }
    after(:build) do |some_model|
      some_model.initializer(some_model)
    end
  end
end

that way you don't have to call it every time you create new object from the factory

Anyway, this initializer method looks like a code smell. I'm not sure what does it do, but maybe there's a better way to do that that also prevents this problem.

Upvotes: 4

Vasfed
Vasfed

Reputation: 18464

FactoryBot calls setters for attributes, not the constructor, so build :some_model, some_attribute: 123, other_attribute: :foo is in fact equivalent of:

SomeModel.new.tap{|m|
  m.some_attribute = 123
  m.other_attribute = :foo
}

This way initializer is called on a empty object and only after that all other attributes are getting set.

When you need to set some attributes that are dependent on others - i'd opt into custom setters like

def some_other_attribute=(val)
  self.some_attribute = calculate(val)
  super
end

or do it in before_validation

Upvotes: 2

Related Questions