The way Solidus was loading FactoryBot factories up until v2.11 was a hack that worked against Rails reloading and made them substantially incompatible with Spring and other tools, in addition to being at odds with the installation instruction that could be found in the FactoryBot and FactoryBotRails respective READMEs.
The basic loading mechanism for factories is still loading the factory file with either
require
or load
. Over time FactoryBot moved to collecting the paths were factories
are defined and loading them in bulk when FactoryBot.find_definitions
is called. Solidus
itself, many of its extensions, and most apps ended up in a mix of calling both require
and find_definitions
.
Usually this was the result of trying to apply FactoryBot's instructions and bumping into some
issue with the loading sequence, or weird errors that ended up being resolved only by a
complete app restart (or spring stop
, etc.).
One of the main issues with Solidus factories used inside your Rails application is that of
factories modifying existing ones. In fact if you add an attribute to, say, Spree::User
you
also probably need to update its factory with code that looks more ore less like this:
spec/factories/users.rb
FactoryBot.modify do
factory :user do
my_custom_column { :foo_bar }
end
end
This kind of factory strictly requires the :user
factory defined by solidus in
[solidus-core-root]/lib/spree/testing_support/factories/user_factory.rb
to be loaded first.
Even worse the situation in which your application needs to modify a factory that is defined in Solidus and then modified by one or more extensions.
Give all of that we needed a solution that could give a way forward without wreaking havoc in all
existing apps and extensions. For this reason doing nothing will just work, and show some deprecations,
but we strongly encourage the upgrade to benefit from app start times (via spring
) and reduced need
for full app restarts.
Things to note down before starting:
- Take a note of your current version of FactoryBot / FactoryBotRails,
use
bundle info factory_bot
orbundle info factory_bot_rails
- Take a note of the factories you depend on, usually Solidus, occasionally extensions,
most of the time those come in the form of requires in your
spec_helper
, e.g.require 'spree/testing_support/factories'
- Make sure you're not cherry-picking factories from Solidus, i.e. you don't have
anything like
require 'spree/testing_support/factories/user_factory'
, check both your spec helper files and your factories
A laundry list of places that could include FactoryBot related code:
- Remove any dependency on
factory_bot
orfactory_bot_rails
from theGemfile
- Remove any
spec_helper.rb
,rails_helper.rb
, orspec/support
code related to FactoryBot, including requires coming - Remove any initialization code in
config/application.rb
or inside initializers, e.g.config/initializers/factory_bot.rb
Add gem 'factory_bot_rails', '~> 4.8'
to your Gemfile
, we advise to use the same version
used by Solidus to ensure you don't get errors from differences in how FactoryBot builds/creates
associations, but if your app was based on a more recent version you might prefer to stick to
that one instead.
Gemfile
group :development, :test do
gem 'factory_bot_rails', '~> 4.8'
end
The initializer should look like this:
config/initializers/factory_bot.rb
Rails.configure do
# Don't initialize this if factory_bot_rails is not part of the
# current bundle group.
if defined?(FactoryBotRails)
initializer after: "factory_bot.set_factory_paths" do
require 'spree/testing_support
# The paths for solidus factories.
solidus_paths = Spree::TestingSupport::FactoryBot.definition_file_paths
# The paths for extensions may be in different places, please refer to each extension
# and add the factories you require without the ".rb" extension, avoid the directory
# as it's supported by FactoryBot but can generate ambigous paths when a file with the
# same name exists.
extension_paths = [
MySolidusExtension::Engine.root.join("lib/solidus_content/factories.rb"),
# alternative 1: MySolidusExtension::Engine.root.join("lib/solidus_content/factories/product.rb"),
# alternative 2: MySolidusExtension::Engine.root.join("lib/solidus_content/factories/product_factory.rb"),
# etc.
].map { |path| path.chomp(".rb") }
# The application factories, according to the place in which they're stored.
app_paths = [
Rails.root.join('lib', 'factories'),
Rails.root.join('spec', 'factories'),
]
FactoryBot.definition_file_paths = solidus_paths + extension_paths + app_paths
end
end
end
As advised by the FactoryBot readme:
spec/support/factory_bot.rb
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
🎉 That's all!