The project is in a healthy, maintained state
FactoryBot extension that wraps FactoryBot::Syntax::Methods to make it a little easier to use
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Runtime

 Project Readme

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.

Automatic association resolution

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.

Autocomplete fully-qualified factory name

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.