FactoryBot::With
FactoryBot::With is a FactoryBot extension that wraps FactoryBot::Syntax::Methods
to make it a little easier to use.
For example, with these factories:
FactoryBot.define do
factory(:blog)
factory(:article) { blog }
factory(:comment) { article }
end
Instead of writing like this:
create(:blog) do |blog|
create(:article, blog:) { |article| create(:comment, article:) }
create(:article, blog:) { |article| create_list(:comment, 3, article:) }
end
FactoryBot::With allows you to write:
create.blog(
with.article(with.comment),
with.article(with_list.comment(3)),
)
Installation
On your Gemfile:
gem "factory_bot-with"
Then, instead of including FactoryBot::Syntax::Methods
, include FactoryBot::With::Methods
:
# RSpec example
RSpec.configure do |config|
# ...
config.include FactoryBot::With::Methods
# ...
end
What differs from FactoryBot::Syntax::Methods
?
Method style syntax
FactoryBot::With overrides the behavior when factory methods are called without arguments.
create(:foo, ...) # behaves in the same way as FactoryBot.create
create # returns a Proxy (an intermadiate) object
create.foo(...) # is equivalent to create(:foo, ...)
This applies to other factory methods such as build_stubbed
, create_list
, with
(described later), etc. as well.
build_stubbed.foo(...)
create_list.foo(10, ...)
Smarter interpretation of positional arguments
FactoryBot::With adjusts the behavior of factory methods to accept Hash
, Array
, and falsy values (false
or nil
) as positional arguments.
create.foo({ title: "Recipe" }, is_new && %i[latest hot])
#=> create(:foo, :latest, :hot, title: "Recipe") if is_new
# create(:foo, title: "Recipe") otherwise
with
, with_pair
, and with_list
operator
FactoryBot::With provides a new operator with
(and its family).
with(:factory_name, ...)
with_pair(:factory_name, ...)
with_list(:factory_name, number_of_items, ...)
The result of this operator (With
instance) can be passed as an argument to factory methods such as build
or create
, which can then create additional objects following the result of the factory.
create.blog(with.article)
# is equivalent to ...
blog = create.blog
create.article(blog:)
The overridden factory methods collect these with
arguments before delegating object creation to the actual factory methods.
with
automatically resolves references to ancestor objects based on the definition of the FactoryBot associations.
This automatic resolution takes into account any traits in the factories, aliases in the factories, and factory specifications in the associations.
FactoryBot.define do
factory(:video)
factory(:photo)
factory(:tag) do
trait(:for_video) { taggable factory: :video }
trait(:for_photo) { taggable factory: :photo }
end
end
create.video(with.tag(text: "latest")) # resolved as taggable: video
create.photo(with.tag(text: "latest")) # ...
Due to technical limitations, inline associations cannot be resolved.
For a factory name that is prefixed by the parent object's factory name, the prefix can be omitted.
FactoryBot.define do
factory(:blog)
factory(:blog_article) { blog }
end
create.blog(with.article) # autocomplete to :blog_article
with
as a template
with
can also be used stand-alone. It works as a template for factory method calls.
Instead of writing:
let(:story) { create(:story, *story_args, **story_kwargs) }
let(:story_args) { [] }
let(:story_kwargs) { {} }
context "when published more than one year ago" do
let(:story_args) { [*super(), :published] }
let(:story_kwargs) { { **super(), start_at: 2.year.ago } }
# ...
end
You can write like this:
# Factory methods accept a With instance as a first argument:
let(:story) { create(story_template) }
let(:story_template) { with.story }
context "when published more than one year ago" do
let(:story_template) { with(super(), :published, start_at: 2.year.ago) }
# ...
end
Development
git clone https://github.com/yubrot/factory_bot-with
cd gems/factory_bot-with
bin/setup
bundle exec rake --tasks
bundle exec rake
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/yubrot/factory_bot-with. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the FactoryBot::With project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.