factory_bot
factory_bot copied to clipboard
`before(:create)` not executed in expected order
Description
The before(:create) block is not getting executed in the order I expect when using instance in one of the factory's associations.
I'm trying to update an existing factory I have (organization) to allow a :build strategy. It currently makes use of a block like this:
after(:create) do |instance, evaluator|
instance.update!(
account_type: evaluator.account_type || build(:account_type, organization: instance),
)
end
Which obviously won't work when using build. So I've updated it by making an association:
account_type { association :account_type, organization: instance }
But now I can't find evidence that the factory's before(:create) block that I need to run is running (at least in the order I expect). See repro steps below.
Reproduction Steps
This fails because the http call is not getting stubbed out:
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "factory_bot", "~> 6.0"
gem "activerecord"
gem "sqlite3"
gem 'webmock'
end
require "active_record"
require "factory_bot"
require "minitest/autorun"
require "logger"
require 'uri'
require 'net/http'
require 'webmock/minitest'
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :organizations, force: true do |t|
t.integer :account_type_id
end
create_table :account_types, force: true do |t|
t.integer :organization_id
end
end
class Organization < ActiveRecord::Base
belongs_to :account_type, optional: true
before_create :get_nasa_image
# [callback] before_create
def get_nasa_image
uri = URI('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY')
Net::HTTP.get_response(uri)
end
end
class AccountType < ActiveRecord::Base
belongs_to :organization
end
FactoryBot.define do
factory :organization do
account_type { association :account_type, organization: instance }
before(:create) do
WebMock.stub_request(:get, "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY").
with(
headers: {
'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Host'=>'api.nasa.gov',
'User-Agent'=>'Ruby'
}).
to_return(status: 200, body: "", headers: {})
end
end
factory :account_type do
organization
end
end
class FactoryBotTest < Minitest::Test
def test_factory_bot_stuff
org = FactoryBot.create(:organization)
assert true
end
end
# Run the tests with `ruby <filename>`
However, if you change the organization factory to this, the http call does in fact get stubbed out and everything works as expected:
factory :organization do
before(:create) do
WebMock.stub_request(:get, "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY").
with(
headers: {
'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Host'=>'api.nasa.gov',
'User-Agent'=>'Ruby'
}).
to_return(status: 200, body: "", headers: {})
end
after(:create) do |instance, evaluator|
instance.update!(
account_type: evaluator.account_type || build(:account_type, organization: instance),
)
end
end
Expected behavior
I expected the factory's before(:create) block to run before the model's after(:create) block.
Actual behavior
The factory's before(:create) block is not run, the HTTP call is not stubbed out, and the test fails because the model is running it's after(:create).
System configuration
factory_bot version: 6.2.1 rails version: 6.1.7.3 ruby version: 2.7.8
I think I'm running into something similar, where factory use in a before(:all) block fails because http calls arent being stubbed. Nothing to add other than that so far 😅
In my project, before(:create) doesn't seem to be run at all. This leads to a situation where
FactoryBot.build(:my_factory).save!
works - the after(:build) callback runs as expected and fills in some bidirectional associations for delegated types correctly.
But
FactoryBot.create(:my_factory)
throws
Validation failed: Data source can't be blank (ActiveRecord::RecordInvalid)
because neither after(:build) nor before(:create) are invoked. This is contrary to the documentation which suggests that FactoryBot.create should run both after(:build) and before(:create) callbacks.