thoughtbot / thoughtbot/factory_bot

`before(:create)` not executed in expected order

Open
#1,573 5 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Ruby
Stars
8.2k
Forks
2.6k
PR merge metrics
No merged PRs in 30d

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the inline reproduction and inspect FactoryBot's callback and association execution path, focusing on how association :account_type, organization: instance affects callback ordering. The work is done when the factory's before(:create) stub runs before the model callback and the reproduction passes.

Written by the indexing model from the issue text.

Assessment

Tech stack
rails, ruby
Domain
testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.