├── .standard.yml ├── docs ├── src │ ├── sequences │ │ ├── summary.md │ │ ├── initial-value.md │ │ ├── with-dynamic-attributes.md │ │ ├── as-implicit-attributes.md │ │ ├── without-a-block.md │ │ ├── rewinding.md │ │ ├── uniqueness.md │ │ ├── global-sequences.md │ │ ├── inline-sequences.md │ │ └── aliases.md │ ├── associations │ │ ├── summary.md │ │ ├── explicit-definition.md │ │ ├── implicit-definition.md │ │ ├── inline-definition.md │ │ ├── specifying-the-factory.md │ │ ├── overriding-attributes.md │ │ ├── association-overrides.md │ │ └── build-strategies.md │ ├── defining │ │ ├── summary.md │ │ ├── static-attributes.md │ │ ├── file-paths.md │ │ ├── name-attributes.md │ │ ├── explicit-class.md │ │ ├── best-practices.md │ │ └── hash-attributes.md │ ├── inheritance │ │ ├── summary.md │ │ ├── best-practices.md │ │ ├── assigning-parent-explicitly.md │ │ └── nested-factories.md │ ├── using-factories │ │ ├── summary.md │ │ ├── build_stubbed-and-marshaldump.md │ │ ├── attribute-overrides.md │ │ └── build-strategies.md │ ├── transient-attributes │ │ ├── with-attributes_for.md │ │ ├── with-other-attributes.md │ │ ├── summary.md │ │ ├── with-associations.md │ │ └── with-callbacks.md │ ├── ref │ │ ├── define.md │ │ ├── trait.md │ │ ├── transient.md │ │ ├── modify.md │ │ ├── association.md │ │ ├── find_definitions.md │ │ ├── lint.md │ │ ├── add_attribute.md │ │ ├── register_strategy.md │ │ ├── build-and-create.md │ │ ├── hooks.md │ │ ├── sequence.md │ │ ├── factory.md │ │ ├── traits_for_enum.md │ │ └── method_missing.md │ ├── traits │ │ ├── traits-within-traits.md │ │ ├── with-transient-attributes.md │ │ ├── as-implicit-attributes.md │ │ ├── in-child-factories.md │ │ ├── mixins.md │ │ ├── attribute-precedence.md │ │ ├── with-associations.md │ │ ├── summary.md │ │ ├── defining-traits.md │ │ ├── using.md │ │ └── enum.md │ ├── callbacks │ │ ├── global-callbacks.md │ │ ├── symbol-to_proc.md │ │ ├── summary.md │ │ ├── default-callbacks.md │ │ └── multiple-callbacks.md │ ├── dependent-attributes │ │ └── summary.md │ ├── method-name-reserved-word-attributes │ │ └── summary.md │ ├── cookbook │ │ ├── polymorphic-associations.md │ │ ├── has_and_belongs_to_many-associations.md │ │ └── interconnected-associations.md │ ├── custom-methods-to-persist-objects │ │ └── summary.md │ ├── using-without-bundler │ │ └── summary.md │ ├── custom-callbacks │ │ └── summary.md │ ├── setup │ │ └── summary.md │ ├── aliases │ │ └── summary.md │ ├── rails-preloaders-and-rspec │ │ └── summary.md │ ├── summary.md │ ├── modifying-factories │ │ └── summary.md │ ├── custom-strategies │ │ └── summary.md │ ├── building-or-creating-multiple-records │ │ └── summary.md │ ├── activesupport-instrumentation │ │ └── summary.md │ └── linting-factories │ │ └── summary.md └── book.toml ├── cucumber.yml ├── .rspec ├── lib └── factory_bot │ ├── version.rb │ ├── syntax_runner.rb │ ├── syntax.rb │ ├── reload.rb │ ├── strategy │ ├── null.rb │ ├── attributes_for.rb │ ├── build.rb │ └── create.rb │ ├── decorator │ ├── new_constructor.rb │ ├── disallows_duplicates_registry.rb │ ├── attribute_hash.rb │ └── invocation_tracker.rb │ ├── attribute │ ├── sequence.rb │ ├── dynamic.rb │ └── association.rb │ ├── aliases.rb │ ├── declaration.rb │ ├── null_object.rb │ ├── callbacks_observer.rb │ ├── null_factory.rb │ ├── attribute.rb │ ├── strategy_calculator.rb │ ├── decorator.rb │ ├── evaluator_class_definer.rb │ ├── declaration │ ├── dynamic.rb │ ├── implicit.rb │ └── association.rb │ ├── evaluation.rb │ ├── enum.rb │ ├── callback.rb │ ├── trait.rb │ ├── find_definitions.rb │ ├── factory_runner.rb │ ├── definition_hierarchy.rb │ ├── configuration.rb │ ├── declaration_list.rb │ ├── errors.rb │ ├── sequence.rb │ ├── registry.rb │ ├── attribute_list.rb │ ├── syntax │ └── default.rb │ ├── strategy_syntax_method_registrar.rb │ ├── evaluator.rb │ └── internal.rb ├── .simplecov ├── .yardopts ├── .gitignore ├── spec ├── support │ ├── matchers │ │ ├── be_about_now.rb │ │ ├── callback.rb │ │ ├── trait.rb │ │ ├── raise_did_you_mean_error.rb │ │ ├── delegate.rb │ │ └── declaration.rb │ └── macros │ │ ├── deprecation.rb │ │ ├── temporary_assignment.rb │ │ └── define_constant.rb ├── factory_bot │ ├── strategy │ │ ├── build_spec.rb │ │ ├── create_spec.rb │ │ ├── attributes_for_spec.rb │ │ └── stub_spec.rb │ ├── attribute_spec.rb │ ├── attribute │ │ ├── sequence_spec.rb │ │ ├── association_spec.rb │ │ └── dynamic_spec.rb │ ├── decorator │ │ └── attribute_hash_spec.rb │ ├── null_object_spec.rb │ ├── callback_spec.rb │ ├── disallows_duplicates_registry_spec.rb │ ├── strategy_calculator_spec.rb │ ├── aliases_spec.rb │ ├── declaration │ │ ├── association_spec.rb │ │ └── dynamic_spec.rb │ ├── null_factory_spec.rb │ ├── evaluator_class_definer_spec.rb │ └── definition_spec.rb ├── acceptance │ ├── definition_without_block_spec.rb │ ├── reload_spec.rb │ ├── definition_camel_string_spec.rb │ ├── private_attributes_spec.rb │ ├── skip_create_spec.rb │ ├── keyed_by_class_spec.rb │ ├── aliases_spec.rb │ ├── define_child_before_parent_spec.rb │ ├── defining_methods_inside_a_factory_spec.rb │ ├── attributes_for_destructuring.rb │ ├── create_pair_spec.rb │ ├── nested_attributes_spec.rb │ ├── attribute_aliases_spec.rb │ ├── add_attribute_spec.rb │ ├── definition_spec.rb │ ├── overrides_spec.rb │ ├── sequence_context_spec.rb │ ├── modify_inherited_spec.rb │ ├── attributes_ordered_spec.rb │ ├── syntax_methods_within_dynamic_attributes_spec.rb │ ├── attributes_from_instance_spec.rb │ ├── parent_spec.rb │ ├── build_list_spec.rb │ ├── attribute_existing_on_object_spec.rb │ ├── sequence_spec.rb │ ├── global_initialize_with_spec.rb │ ├── build_spec.rb │ └── attributes_for_spec.rb ├── factory_bot_spec.rb └── spec_helper.rb ├── Gemfile ├── CODE_OF_CONDUCT.md ├── gemfiles ├── 7.1.gemfile ├── 6.1.gemfile ├── 7.0.gemfile └── main.gemfile ├── features ├── step_definitions │ ├── database_steps.rb │ └── factory_bot_steps.rb ├── support │ ├── env.rb │ └── factories.rb └── find_definitions.feature ├── CODEOWNERS ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── workflows │ ├── docs.yml │ └── build.yml └── REPRODUCTION_SCRIPT.rb ├── NAME.md ├── Appraisals ├── Rakefile ├── LICENSE ├── RELEASING.md └── factory_bot.gemspec /.standard.yml: -------------------------------------------------------------------------------- 1 | ruby_version: "3.0" 2 | -------------------------------------------------------------------------------- /docs/src/sequences/summary.md: -------------------------------------------------------------------------------- 1 | # Sequences 2 | -------------------------------------------------------------------------------- /docs/src/associations/summary.md: -------------------------------------------------------------------------------- 1 | # Associations 2 | -------------------------------------------------------------------------------- /docs/src/defining/summary.md: -------------------------------------------------------------------------------- 1 | # Defining factories 2 | -------------------------------------------------------------------------------- /docs/src/inheritance/summary.md: -------------------------------------------------------------------------------- 1 | # Inheritance 2 | -------------------------------------------------------------------------------- /docs/src/using-factories/summary.md: -------------------------------------------------------------------------------- 1 | # Using factories 2 | -------------------------------------------------------------------------------- /cucumber.yml: -------------------------------------------------------------------------------- 1 | default: --publish-quiet -r features features 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format progress 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /lib/factory_bot/version.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | VERSION = "6.4.5".freeze 3 | end 4 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | SimpleCov.start do 2 | add_filter "/spec/" 3 | add_filter "/tmp/" 4 | end 5 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | - 3 | GETTING_STARTED.md 4 | CONTRIBUTING.md 5 | NAME.md 6 | LICENSE 7 | -------------------------------------------------------------------------------- /lib/factory_bot/syntax_runner.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | class SyntaxRunner 4 | include Syntax::Methods 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/factory_bot/syntax.rb: -------------------------------------------------------------------------------- 1 | require "factory_bot/syntax/methods" 2 | require "factory_bot/syntax/default" 3 | 4 | module FactoryBot 5 | module Syntax 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | test.db 3 | factory_girl-*.gem 4 | factory_bot-*.gem 5 | docs/book 6 | .yardoc 7 | coverage 8 | .bundle 9 | tmp 10 | bin 11 | .rubocop-https* 12 | -------------------------------------------------------------------------------- /spec/support/matchers/be_about_now.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :be_about_now do 2 | match do |actual| 3 | expect(actual).to be_within(2.seconds).of(Time.now) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activerecord-jdbcsqlite3-adapter", platforms: [:jruby] 4 | gem "sqlite3", platforms: [:ruby] 5 | 6 | gemspec name: "factory_bot" 7 | -------------------------------------------------------------------------------- /lib/factory_bot/reload.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | def self.reload 3 | Internal.reset_configuration 4 | Internal.register_default_strategies 5 | find_definitions 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | 3 | By participating in this project, you agree to abide by the 4 | [thoughtbot code of conduct][1]. 5 | 6 | [1]: https://thoughtbot.com/open-source-code-of-conduct 7 | -------------------------------------------------------------------------------- /docs/src/transient-attributes/with-attributes_for.md: -------------------------------------------------------------------------------- 1 | # With attributes_for 2 | 3 | Transient attributes will be ignored within `attributes_for` and won't be set 4 | on the model, even if the attribute exists or you attempt to override it. 5 | -------------------------------------------------------------------------------- /docs/src/using-factories/build_stubbed-and-marshaldump.md: -------------------------------------------------------------------------------- 1 | # build_stubbed and Marshal.dump 2 | 3 | Note that objects created with `build_stubbed` cannot be serialized with 4 | `Marshal.dump`, since factory\_bot defines singleton methods on these objects. 5 | -------------------------------------------------------------------------------- /docs/src/inheritance/best-practices.md: -------------------------------------------------------------------------------- 1 | # Best practices 2 | 3 | As mentioned above, it's good practice to define a basic factory for each class 4 | with only the attributes required to create it. Then, create more specific 5 | factories that inherit from this basic parent. 6 | -------------------------------------------------------------------------------- /docs/src/ref/define.md: -------------------------------------------------------------------------------- 1 | # FactoryBot.define 2 | 3 | Each file loaded by factory\_bot is expected to call `FactoryBot.define` with a 4 | block. The block is evaluated within an instance of 5 | `FactoryBot::Syntax::Default::DSL`, giving access to `factory`, `sequence`, 6 | `trait`, and other methods. 7 | -------------------------------------------------------------------------------- /lib/factory_bot/strategy/null.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | module Strategy 3 | class Null 4 | def association(runner) 5 | end 6 | 7 | def result(evaluation) 8 | end 9 | 10 | def to_sym 11 | :null 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/factory_bot/strategy/build_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Strategy::Build do 2 | it_should_behave_like "strategy with association support", :create 3 | it_should_behave_like "strategy with callbacks", :after_build 4 | it_should_behave_like "strategy with strategy: :build", :build 5 | end 6 | -------------------------------------------------------------------------------- /gemfiles/7.1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "activerecord-jdbcsqlite3-adapter", platforms: [:jruby] 6 | gem "sqlite3", platforms: [:ruby] 7 | gem "activerecord", "~> 7.1.0" 8 | 9 | gemspec name: "factory_bot", path: "../" 10 | -------------------------------------------------------------------------------- /spec/support/matchers/callback.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :have_callback do |callback_name| 2 | match do |instance| 3 | instance.callbacks.include?(FactoryBot::Callback.new(callback_name, @block)) 4 | end 5 | 6 | chain :with_block do |block| 7 | @block = block 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /docs/src/associations/explicit-definition.md: -------------------------------------------------------------------------------- 1 | # Explicit definition 2 | 3 | You can define associations explicitly. This can be handy especially when 4 | [Overriding attributes](overriding-attributes.md) 5 | 6 | ```ruby 7 | factory :post do 8 | # ... 9 | association :author 10 | end 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/src/defining/static-attributes.md: -------------------------------------------------------------------------------- 1 | # Static Attributes 2 | 3 | Static attributes (without a block) are no longer available in factory\_bot 5. 4 | You can read more about the decision to remove them in 5 | [this blog post](https://robots.thoughtbot.com/deprecating-static-attributes-in-factory_bot-4-11). 6 | -------------------------------------------------------------------------------- /docs/src/inheritance/assigning-parent-explicitly.md: -------------------------------------------------------------------------------- 1 | # Assigning parent explicitly 2 | 3 | You can also assign the parent explicitly: 4 | 5 | ```ruby 6 | factory :post do 7 | title { "A title" } 8 | end 9 | 10 | factory :approved_post, parent: :post do 11 | approved { true } 12 | end 13 | ``` 14 | -------------------------------------------------------------------------------- /features/step_definitions/database_steps.rb: -------------------------------------------------------------------------------- 1 | Then(/^I should find the following for the last category:$/) do |table| 2 | table.hashes.first.each do |key, value| 3 | expect(Category.last.attributes[key].to_s).to eq value 4 | end 5 | end 6 | 7 | Before do 8 | Category.delete_all 9 | end 10 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..", "..")) 2 | 3 | require "simplecov" if RUBY_ENGINE == "ruby" 4 | 5 | $: << File.join(PROJECT_ROOT, "lib") 6 | 7 | require "active_record" 8 | require "factory_bot" 9 | 10 | require "aruba/cucumber" 11 | -------------------------------------------------------------------------------- /spec/support/macros/deprecation.rb: -------------------------------------------------------------------------------- 1 | require "active_support" 2 | 3 | RSpec.configure do |config| 4 | config.around :example, silence_deprecation: true do |example| 5 | with_temporary_assignment(FactoryBot::Deprecation, :silenced, true) do 6 | example.run 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /docs/src/sequences/initial-value.md: -------------------------------------------------------------------------------- 1 | # Initial value 2 | 3 | You can override the initial value. Any value that responds to the `#next` 4 | method will work (e.g. 1, 2, 3, 'a', 'b', 'c') 5 | 6 | ```ruby 7 | factory :user do 8 | sequence(:email, 1000) { |n| "person#{n}@example.com" } 9 | end 10 | ``` 11 | -------------------------------------------------------------------------------- /gemfiles/6.1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "activerecord-jdbcsqlite3-adapter", "~> 61.0", platforms: [:jruby] 6 | gem "sqlite3", platforms: [:ruby] 7 | gem "activerecord", "~> 6.1.0" 8 | 9 | gemspec name: "factory_bot", path: "../" 10 | -------------------------------------------------------------------------------- /gemfiles/7.0.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "activerecord-jdbcsqlite3-adapter", "~> 70.0", platforms: [:jruby] 6 | gem "sqlite3", platforms: [:ruby] 7 | gem "activerecord", "~> 7.0.0" 8 | 9 | gemspec name: "factory_bot", path: "../" 10 | -------------------------------------------------------------------------------- /docs/src/associations/implicit-definition.md: -------------------------------------------------------------------------------- 1 | # Implicit definition 2 | 3 | It's possible to set up associations within factories. If the factory name is 4 | the same as the association name, the factory name can be left out. 5 | 6 | ```ruby 7 | factory :post do 8 | # ... 9 | author 10 | end 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/src/associations/inline-definition.md: -------------------------------------------------------------------------------- 1 | # Inline definition 2 | 3 | You can also define associations inline within regular attributes, but note 4 | that the value will be `nil` when using the `attributes_for` strategy. 5 | 6 | ```ruby 7 | factory :post do 8 | # ... 9 | author { association :author } 10 | end 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/src/sequences/with-dynamic-attributes.md: -------------------------------------------------------------------------------- 1 | # With dynamic attributes 2 | 3 | Sequences can be used in dynamic attributes: 4 | 5 | ```ruby 6 | FactoryBot.define do 7 | sequence :email do |n| 8 | "person#{n}@example.com" 9 | end 10 | end 11 | 12 | factory :invite do 13 | invitee { generate(:email) } 14 | end 15 | ``` 16 | -------------------------------------------------------------------------------- /spec/support/matchers/trait.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :have_trait do |trait_name| 2 | match do |instance| 3 | instance.defined_traits.any? do |trait| 4 | trait.name == trait_name.to_s && trait.send(:block) == @block 5 | end 6 | end 7 | 8 | chain :with_block do |block| 9 | @block = block 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/factory_bot/decorator/new_constructor.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Decorator 3 | class NewConstructor < Decorator 4 | def initialize(component, build_class) 5 | super(component) 6 | @build_class = build_class 7 | end 8 | 9 | delegate :new, to: :@build_class 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /gemfiles/main.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "activerecord-jdbcsqlite3-adapter", "~> 70.0", platforms: [:jruby] 6 | gem "sqlite3", platforms: [:ruby] 7 | gem "activerecord", git: "https://github.com/rails/rails.git", branch: "main" 8 | 9 | gemspec name: "factory_bot", path: "../" 10 | -------------------------------------------------------------------------------- /docs/src/defining/file-paths.md: -------------------------------------------------------------------------------- 1 | # Definition file paths 2 | 3 | Factories can be defined anywhere, but will be automatically loaded after 4 | calling `FactoryBot.find_definitions` if factories are defined in files at the 5 | following locations: 6 | 7 | test/factories.rb 8 | spec/factories.rb 9 | test/factories/*.rb 10 | spec/factories/*.rb 11 | -------------------------------------------------------------------------------- /docs/src/traits/traits-within-traits.md: -------------------------------------------------------------------------------- 1 | # Traits within traits 2 | 3 | Traits can be used within other traits to mix in their attributes. 4 | 5 | ```ruby 6 | factory :order do 7 | trait :completed do 8 | completed_at { 3.days.ago } 9 | end 10 | 11 | trait :refunded do 12 | completed 13 | refunded_at { 1.day.ago } 14 | end 15 | end 16 | ``` 17 | -------------------------------------------------------------------------------- /lib/factory_bot/strategy/attributes_for.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | module Strategy 3 | class AttributesFor 4 | def association(runner) 5 | runner.run(:null) 6 | end 7 | 8 | def result(evaluation) 9 | evaluation.hash 10 | end 11 | 12 | def to_sym 13 | :attributes_for 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /docs/src/ref/trait.md: -------------------------------------------------------------------------------- 1 | # trait 2 | 3 | Within a `factory` definition block, use the `trait` method to define named permutations of the factory. 4 | 5 | The trait method takes a name (Symbol) and a block. Treat the block like you 6 | would a [`factory`] definition block. 7 | 8 | [`factory`]: factory.html 9 | 10 | See [method_missing](method_missing.html) for a shorthand. 11 | -------------------------------------------------------------------------------- /spec/acceptance/definition_without_block_spec.rb: -------------------------------------------------------------------------------- 1 | describe "an instance generated by a factory" do 2 | before do 3 | define_model("User") 4 | 5 | FactoryBot.define do 6 | factory :user 7 | end 8 | end 9 | 10 | it "registers the user factory" do 11 | expect(FactoryBot::Internal.factory_by_name(:user)) 12 | .to be_a(FactoryBot::Factory) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/acceptance/reload_spec.rb: -------------------------------------------------------------------------------- 1 | describe "reload" do 2 | it "does not reset the value of use_parent_strategy" do 3 | custom_strategy = :custom_use_parent_strategy_value 4 | 5 | with_temporary_assignment(FactoryBot, :use_parent_strategy, custom_strategy) do 6 | FactoryBot.reload 7 | expect(FactoryBot.use_parent_strategy).to eq custom_strategy 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /docs/src/defining/name-attributes.md: -------------------------------------------------------------------------------- 1 | # Factory name and attributes 2 | 3 | Each factory has a name and a set of attributes. The name is used to guess the 4 | class of the object by default: 5 | 6 | ```ruby 7 | # This will guess the User class 8 | FactoryBot.define do 9 | factory :user do 10 | first_name { "John" } 11 | last_name { "Doe" } 12 | admin { false } 13 | end 14 | end 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/src/callbacks/global-callbacks.md: -------------------------------------------------------------------------------- 1 | # Global callbacks 2 | 3 | To override callbacks for all factories, define them within the 4 | `FactoryBot.define` block: 5 | 6 | ```ruby 7 | FactoryBot.define do 8 | after(:build) { |object| puts "Built #{object}" } 9 | after(:create) { |object| AuditLog.create(attrs: object.attributes) } 10 | 11 | factory :user do 12 | name { "John Doe" } 13 | end 14 | end 15 | ``` 16 | -------------------------------------------------------------------------------- /lib/factory_bot/attribute/sequence.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Attribute 3 | # @api private 4 | class Sequence < Attribute 5 | def initialize(name, sequence, ignored) 6 | super(name, ignored) 7 | @sequence = sequence 8 | end 9 | 10 | def to_proc 11 | sequence = @sequence 12 | -> { FactoryBot.generate(sequence) } 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/factory_bot/strategy/build.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | module Strategy 3 | class Build 4 | def association(runner) 5 | runner.run 6 | end 7 | 8 | def result(evaluation) 9 | evaluation.object.tap do |instance| 10 | evaluation.notify(:after_build, instance) 11 | end 12 | end 13 | 14 | def to_sym 15 | :build 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/factory_bot_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot do 2 | it "finds a registered strategy" do 3 | FactoryBot.register_strategy(:strategy_name, :strategy_class) 4 | expect(FactoryBot.strategy_by_name(:strategy_name)) 5 | .to eq :strategy_class 6 | end 7 | 8 | describe ".use_parent_strategy" do 9 | it "is true by default" do 10 | expect(FactoryBot.use_parent_strategy).to be true 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /features/support/factories.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Base.establish_connection( 2 | adapter: "sqlite3", 3 | database: ":memory:" 4 | ) 5 | 6 | class CreateSchema < ActiveRecord::Migration[5.0] 7 | def self.up 8 | create_table :categories, force: true do |t| 9 | t.string :name 10 | end 11 | end 12 | end 13 | 14 | CreateSchema.suppress_messages { CreateSchema.migrate(:up) } 15 | 16 | class Category < ActiveRecord::Base; end 17 | -------------------------------------------------------------------------------- /lib/factory_bot/decorator/disallows_duplicates_registry.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Decorator 3 | class DisallowsDuplicatesRegistry < Decorator 4 | def register(name, item) 5 | if registered?(name) 6 | raise DuplicateDefinitionError, "#{@component.name} already registered: #{name}" 7 | else 8 | @component.register(name, item) 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/factory_bot/aliases.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class << self 3 | attr_accessor :aliases 4 | end 5 | 6 | self.aliases = [ 7 | [/(.+)_id/, '\1'], 8 | [/(.*)/, '\1_id'] 9 | ] 10 | 11 | def self.aliases_for(attribute) 12 | aliases.map { |(pattern, replace)| 13 | if pattern.match?(attribute) 14 | attribute.to_s.sub(pattern, replace).to_sym 15 | end 16 | }.compact << attribute 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /docs/src/dependent-attributes/summary.md: -------------------------------------------------------------------------------- 1 | # Dependent Attributes 2 | 3 | Attributes can be based on the values of other attributes using the context 4 | that is yielded to dynamic attribute blocks: 5 | 6 | ```ruby 7 | factory :user do 8 | first_name { "Joe" } 9 | last_name { "Blow" } 10 | email { "#{first_name}.#{last_name}@example.com".downcase } 11 | end 12 | 13 | create(:user, last_name: "Doe").email 14 | # => "joe.doe@example.com" 15 | ``` 16 | -------------------------------------------------------------------------------- /spec/acceptance/definition_camel_string_spec.rb: -------------------------------------------------------------------------------- 1 | describe "an instance generated by a factory named a camel case string " do 2 | before do 3 | define_model("UserModel") 4 | 5 | FactoryBot.define do 6 | factory "UserModel", class: UserModel 7 | end 8 | end 9 | 10 | it "registers the UserModel factory" do 11 | expect(FactoryBot::Internal.factory_by_name("UserModel")) 12 | .to be_a(FactoryBot::Factory) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/acceptance/private_attributes_spec.rb: -------------------------------------------------------------------------------- 1 | describe "setting private attributes" do 2 | it "raises a NoMethodError" do 3 | define_class("User") do 4 | private 5 | 6 | attr_accessor :foo 7 | end 8 | 9 | FactoryBot.define do 10 | factory :user do 11 | foo { 123 } 12 | end 13 | end 14 | 15 | expect { 16 | FactoryBot.build(:user) 17 | }.to raise_error NoMethodError, /foo=/ 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /docs/src/traits/with-transient-attributes.md: -------------------------------------------------------------------------------- 1 | # With transient attributes 2 | 3 | Traits can accept transient attributes. 4 | 5 | ```ruby 6 | factory :invoice do 7 | trait :with_amount do 8 | transient do 9 | amount { 1 } 10 | end 11 | 12 | after(:create) do |invoice, context| 13 | create :line_item, invoice: invoice, amount: context.amount 14 | end 15 | end 16 | end 17 | 18 | create :invoice, :with_amount, amount: 2 19 | ``` 20 | -------------------------------------------------------------------------------- /spec/acceptance/skip_create_spec.rb: -------------------------------------------------------------------------------- 1 | describe "skipping the default create" do 2 | before do 3 | define_model("User", email: :string) 4 | 5 | FactoryBot.define do 6 | factory :user do 7 | skip_create 8 | 9 | email { "john@example.com" } 10 | end 11 | end 12 | end 13 | 14 | it "doesn't execute anything when creating the instance" do 15 | expect(FactoryBot.create(:user)).not_to be_persisted 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /docs/src/sequences/as-implicit-attributes.md: -------------------------------------------------------------------------------- 1 | # As implicit attributes 2 | 3 | Or as implicit attributes: 4 | 5 | ```ruby 6 | FactoryBot.define do 7 | sequence :email do |n| 8 | "person#{n}@example.com" 9 | end 10 | end 11 | 12 | factory :user do 13 | email # Same as `email { generate(:email) }` 14 | end 15 | ``` 16 | 17 | Note that defining sequences as implicit attributes will not work if you have a 18 | factory with the same name as the sequence. 19 | -------------------------------------------------------------------------------- /docs/src/sequences/without-a-block.md: -------------------------------------------------------------------------------- 1 | # Without a block 2 | 3 | Without a block, the value will increment itself, starting at its initial value: 4 | 5 | ```ruby 6 | factory :post do 7 | sequence(:position) 8 | end 9 | ``` 10 | 11 | Note that the value for the sequence could be any Enumerable instance, as long 12 | as it responds to `#next`: 13 | 14 | ```ruby 15 | factory :task do 16 | sequence :priority, %i[low medium high urgent].cycle 17 | end 18 | ``` 19 | -------------------------------------------------------------------------------- /spec/factory_bot/attribute_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Attribute do 2 | it "converts the name attribute to a symbol" do 3 | name = "user" 4 | attribute = FactoryBot::Attribute.new(name, false) 5 | 6 | expect(attribute.name).to eq name.to_sym 7 | end 8 | 9 | it "is not an association" do 10 | name = "user" 11 | attribute = FactoryBot::Attribute.new(name, false) 12 | 13 | expect(attribute).not_to be_association 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /docs/src/callbacks/symbol-to_proc.md: -------------------------------------------------------------------------------- 1 | # Symbol#to_proc 2 | 3 | You can call callbacks that rely on `Symbol#to_proc`: 4 | 5 | ```ruby 6 | # app/models/user.rb 7 | class User < ActiveRecord::Base 8 | def confirm! 9 | # confirm the user account 10 | end 11 | end 12 | 13 | # spec/factories.rb 14 | FactoryBot.define do 15 | factory :user do 16 | after :create, &:confirm! 17 | end 18 | end 19 | 20 | create(:user) # creates the user and confirms it 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/src/inheritance/nested-factories.md: -------------------------------------------------------------------------------- 1 | # Nested factories 2 | 3 | You can create multiple factories for the same class without repeating common 4 | attributes by nesting factories: 5 | 6 | ```ruby 7 | factory :post do 8 | title { "A title" } 9 | 10 | factory :approved_post do 11 | approved { true } 12 | end 13 | end 14 | 15 | approved_post = create(:approved_post) 16 | approved_post.title # => "A title" 17 | approved_post.approved # => true 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/src/traits/as-implicit-attributes.md: -------------------------------------------------------------------------------- 1 | # As implicit attributes 2 | 3 | Traits can be used as implicit attributes: 4 | 5 | ```ruby 6 | factory :week_long_published_story_with_title, parent: :story do 7 | published 8 | week_long_publishing 9 | title { "Publishing that was started at #{start_at}" } 10 | end 11 | ``` 12 | 13 | Note that defining traits as implicit attributes will not work if you have a 14 | factory or sequence with the same name as the trait. 15 | -------------------------------------------------------------------------------- /docs/src/traits/in-child-factories.md: -------------------------------------------------------------------------------- 1 | # In child factories 2 | 3 | You can override individual attributes granted by a trait in a child factory: 4 | 5 | ```ruby 6 | factory :user do 7 | name { "Friendly User" } 8 | login { name } 9 | 10 | trait :active do 11 | name { "John Doe" } 12 | status { :active } 13 | login { "#{name} (M)" } 14 | end 15 | 16 | factory :brandon do 17 | active 18 | name { "Brandon" } 19 | end 20 | end 21 | ``` 22 | -------------------------------------------------------------------------------- /spec/acceptance/keyed_by_class_spec.rb: -------------------------------------------------------------------------------- 1 | describe "finding factories keyed by class instead of symbol" do 2 | before do 3 | define_model("User") do 4 | attr_accessor :name, :email 5 | end 6 | 7 | FactoryBot.define do 8 | factory :user 9 | end 10 | end 11 | 12 | it "doesn't find the factory" do 13 | expect { FactoryBot.create(User) }.to( 14 | raise_error(KeyError, /Factory not registered: User/) 15 | ) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /docs/src/sequences/rewinding.md: -------------------------------------------------------------------------------- 1 | # Rewinding 2 | 3 | Sequences can also be rewound with `FactoryBot.rewind_sequences`: 4 | 5 | ```ruby 6 | sequence(:email) {|n| "person#{n}@example.com" } 7 | 8 | generate(:email) # "person1@example.com" 9 | generate(:email) # "person2@example.com" 10 | generate(:email) # "person3@example.com" 11 | 12 | FactoryBot.rewind_sequences 13 | 14 | generate(:email) # "person1@example.com" 15 | ``` 16 | 17 | This rewinds all registered sequences. 18 | -------------------------------------------------------------------------------- /spec/acceptance/aliases_spec.rb: -------------------------------------------------------------------------------- 1 | describe "aliases and overrides" do 2 | before do 3 | FactoryBot.aliases << [/one/, "two"] 4 | 5 | define_model("User", two: :string, one: :string) 6 | 7 | FactoryBot.define do 8 | factory :user do 9 | two { "set value" } 10 | end 11 | end 12 | end 13 | 14 | subject { FactoryBot.create(:user, one: "override") } 15 | its(:one) { should eq "override" } 16 | its(:two) { should be_nil } 17 | end 18 | -------------------------------------------------------------------------------- /lib/factory_bot/decorator/attribute_hash.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Decorator 3 | class AttributeHash < Decorator 4 | def initialize(component, attributes = []) 5 | super(component) 6 | @attributes = attributes 7 | end 8 | 9 | def attributes 10 | @attributes.each_with_object({}) do |attribute_name, result| 11 | result[attribute_name] = @component.send(attribute_name) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docs/src/traits/mixins.md: -------------------------------------------------------------------------------- 1 | # As mixins 2 | 3 | Traits can be defined outside of factories and used as mixins to compose shared 4 | attributes: 5 | 6 | ```ruby 7 | FactoryBot.define do 8 | trait :timestamps do 9 | created_at { 8.days.ago } 10 | updated_at { 4.days.ago } 11 | end 12 | 13 | factory :user, traits: [:timestamps] do 14 | username { "john_doe" } 15 | end 16 | 17 | factory :post do 18 | timestamps 19 | title { "Traits rock" } 20 | end 21 | end 22 | ``` 23 | -------------------------------------------------------------------------------- /spec/support/macros/temporary_assignment.rb: -------------------------------------------------------------------------------- 1 | module TemporaryAssignment 2 | def with_temporary_assignment(assignee, attribute, temporary_value) 3 | original_value = assignee.public_send(attribute) 4 | attribute_setter = "#{attribute}=" 5 | assignee.public_send(attribute_setter, temporary_value) 6 | yield 7 | ensure 8 | assignee.public_send(attribute_setter, original_value) 9 | end 10 | end 11 | 12 | RSpec.configure do |config| 13 | config.include TemporaryAssignment 14 | end 15 | -------------------------------------------------------------------------------- /docs/src/transient-attributes/with-other-attributes.md: -------------------------------------------------------------------------------- 1 | # With other attributes 2 | 3 | You can access transient attributes within other attributes (see [Dependent 4 | Attributes](../dependent-attributes/summary.md)): 5 | 6 | ```ruby 7 | factory :user do 8 | transient do 9 | rockstar { true } 10 | end 11 | 12 | name { "John Doe#{" - Rockstar" if rockstar}" } 13 | end 14 | 15 | create(:user).name 16 | #=> "John Doe - ROCKSTAR" 17 | 18 | create(:user, rockstar: false).name 19 | #=> "John Doe" 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/src/sequences/uniqueness.md: -------------------------------------------------------------------------------- 1 | # Uniqueness 2 | 3 | When working with uniqueness constraints, be careful not to pass in override 4 | values that will conflict with the generated sequence values. 5 | 6 | In this example the email will be the same for both users. If email must be 7 | unique, this code will error: 8 | 9 | ```rb 10 | factory :user do 11 | sequence(:email) { |n| "person#{n}@example.com" } 12 | end 13 | 14 | FactoryBot.create(:user, email: "person1@example.com") 15 | FactoryBot.create(:user) 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/src/transient-attributes/summary.md: -------------------------------------------------------------------------------- 1 | # Transient Attributes 2 | 3 | Transient attributes are attributes only available within the factory 4 | definition, and not set on the object being built. This allows for more complex 5 | logic inside factories. 6 | 7 | These are defined within a `transient` block: 8 | 9 | ```ruby 10 | factory :user do 11 | name { "Zero Cool" } 12 | birth_date { age&.years.ago } 13 | 14 | transient do 15 | age { 11 } # only used to set `birth_date` above 16 | end 17 | end 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/src/transient-attributes/with-associations.md: -------------------------------------------------------------------------------- 1 | # With associations 2 | 3 | Transient [associations](../associations/summary.md) are not supported in 4 | factory\_bot. Associations within the transient block will be treated as 5 | regular, non-transient associations. 6 | 7 | If needed, you can generally work around this by building a factory within a 8 | transient attribute: 9 | 10 | ```ruby 11 | factory :post 12 | 13 | factory :user do 14 | transient do 15 | post { build(:post) } 16 | end 17 | end 18 | ``` 19 | -------------------------------------------------------------------------------- /lib/factory_bot/declaration.rb: -------------------------------------------------------------------------------- 1 | require "factory_bot/declaration/dynamic" 2 | require "factory_bot/declaration/association" 3 | require "factory_bot/declaration/implicit" 4 | 5 | module FactoryBot 6 | # @api private 7 | class Declaration 8 | attr_reader :name 9 | 10 | def initialize(name, ignored = false) 11 | @name = name 12 | @ignored = ignored 13 | end 14 | 15 | def to_attributes 16 | build 17 | end 18 | 19 | protected 20 | 21 | attr_reader :ignored 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /docs/src/method-name-reserved-word-attributes/summary.md: -------------------------------------------------------------------------------- 1 | # Method Name / Reserved Word Attributes 2 | 3 | If your attributes conflict with existing methods or reserved words (all 4 | methods in the 5 | [DefinitionProxy](https://github.com/thoughtbot/factory_bot/blob/main/lib/factory_bot/definition_proxy.rb) 6 | class) you can define them with `add_attribute`. 7 | 8 | ```ruby 9 | factory :dna do 10 | add_attribute(:sequence) { 'GATTACA' } 11 | end 12 | 13 | factory :payment do 14 | add_attribute(:method) { 'paypal' } 15 | end 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/src/ref/transient.md: -------------------------------------------------------------------------------- 1 | # transient 2 | 3 | Within a `factory` definition block, the goal is to construct an instance of 4 | the class. While factory\_bot does this, it keeps track of data in a 5 | context. To set data on this context, use a `transient` block. 6 | 7 | Treat a `transient` block like a `factory` definition block. However, none of 8 | the attributes, associations, traits, or sequences you set will impact the 9 | final object. 10 | 11 | This is most useful when paired with [hooks](hooks.html) or 12 | [to_create](build-and-create.html). 13 | -------------------------------------------------------------------------------- /spec/factory_bot/strategy/create_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Strategy::Create do 2 | it_should_behave_like "strategy with association support", :create 3 | it_should_behave_like "strategy with callbacks", :after_build, :before_create, :after_create 4 | 5 | it "runs a custom create block" do 6 | evaluation = double( 7 | "evaluation", 8 | object: nil, 9 | notify: nil, 10 | create: nil 11 | ) 12 | 13 | subject.result(evaluation) 14 | 15 | expect(evaluation).to have_received(:create).once 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /docs/src/defining/explicit-class.md: -------------------------------------------------------------------------------- 1 | # Specifying the class explicitly 2 | 3 | It is also possible to explicitly specify the class: 4 | 5 | ```ruby 6 | # This will use the User class (otherwise Admin would have been guessed) 7 | factory :admin, class: "User" 8 | ``` 9 | 10 | You can pass a constant as well, if the constant is available (note that this 11 | can cause test performance problems in large Rails applications, since 12 | referring to the constant will cause it to be eagerly loaded). 13 | 14 | ```ruby 15 | factory :access_token, class: User 16 | ``` 17 | -------------------------------------------------------------------------------- /lib/factory_bot/null_object.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | class NullObject < ::BasicObject 4 | def initialize(methods_to_respond_to) 5 | @methods_to_respond_to = methods_to_respond_to.map(&:to_s) 6 | end 7 | 8 | def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing 9 | if respond_to?(name) 10 | nil 11 | else 12 | super 13 | end 14 | end 15 | 16 | def respond_to?(method) 17 | @methods_to_respond_to.include? method.to_s 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/factory_bot/attribute/sequence_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Attribute::Sequence do 2 | let(:sequence_name) { :name } 3 | let(:name) { :first_name } 4 | let(:sequence) { FactoryBot::Sequence.new(sequence_name, 5) { |n| "Name #{n}" } } 5 | 6 | subject { FactoryBot::Attribute::Sequence.new(name, sequence_name, false) } 7 | before { FactoryBot::Internal.register_sequence(sequence) } 8 | 9 | its(:name) { should eq name } 10 | 11 | it "assigns the next value in the sequence" do 12 | expect(subject.to_proc.call).to eq "Name 5" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /docs/src/associations/specifying-the-factory.md: -------------------------------------------------------------------------------- 1 | # Specifying the factory 2 | 3 | You can specify a different factory (although [Aliases](../aliases/summary.md) 4 | might also help you out here). 5 | 6 | Implicitly: 7 | 8 | ```ruby 9 | factory :post do 10 | # ... 11 | author factory: :user 12 | end 13 | ``` 14 | 15 | Explicitly: 16 | 17 | ```ruby 18 | factory :post do 19 | # ... 20 | association :author, factory: :user 21 | end 22 | ``` 23 | 24 | Inline: 25 | 26 | ```ruby 27 | factory :post do 28 | # ... 29 | author { association :user } 30 | end 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/src/defining/best-practices.md: -------------------------------------------------------------------------------- 1 | # Best practices 2 | 3 | It is recommended that you have one factory for each class that provides 4 | the simplest set of attributes necessary to create an instance of that class. If 5 | you're creating ActiveRecord objects, that means that you should only provide 6 | attributes that are required through validations and that do not have defaults. 7 | Other factories can be created through inheritance to cover common scenarios for 8 | each class. 9 | 10 | Attempting to define multiple factories with the same name will raise an error. 11 | -------------------------------------------------------------------------------- /lib/factory_bot/callbacks_observer.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | class CallbacksObserver 4 | def initialize(callbacks, evaluator) 5 | @callbacks = callbacks 6 | @evaluator = evaluator 7 | end 8 | 9 | def update(name, result_instance) 10 | callbacks_by_name(name).each do |callback| 11 | callback.run(result_instance, @evaluator) 12 | end 13 | end 14 | 15 | private 16 | 17 | def callbacks_by_name(name) 18 | @callbacks.select { |callback| callback.name == name } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/factory_bot/strategy/attributes_for_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Strategy::AttributesFor do 2 | let(:result) { {name: "John Doe", gender: "Male", admin: false} } 3 | let(:evaluation) { double("evaluation", hash: result) } 4 | 5 | it_should_behave_like "strategy without association support" 6 | 7 | it "returns the hash from the evaluation" do 8 | expect(subject.result(evaluation)).to eq result 9 | end 10 | 11 | it "does not run the to_create block" do 12 | expect { 13 | subject.result(evaluation) 14 | }.to_not raise_error 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docs/src/sequences/global-sequences.md: -------------------------------------------------------------------------------- 1 | # Global sequences 2 | 3 | Unique values in a specific format (for example, e-mail addresses) can be 4 | generated using sequences. Sequences are defined by calling `sequence` in a 5 | definition block, and values in a sequence are generated by calling `generate`: 6 | 7 | ```ruby 8 | # Defines a new sequence 9 | FactoryBot.define do 10 | sequence :email do |n| 11 | "person#{n}@example.com" 12 | end 13 | end 14 | 15 | generate :email 16 | # => "person1@example.com" 17 | 18 | generate :email 19 | # => "person2@example.com" 20 | ``` 21 | -------------------------------------------------------------------------------- /spec/acceptance/define_child_before_parent_spec.rb: -------------------------------------------------------------------------------- 1 | describe "defining a child factory before a parent" do 2 | before do 3 | define_model("User", name: :string, admin: :boolean, email: :string, upper_email: :string, login: :string) 4 | 5 | FactoryBot.define do 6 | factory :admin, parent: :user do 7 | admin { true } 8 | end 9 | 10 | factory :user do 11 | name { "awesome" } 12 | end 13 | end 14 | end 15 | 16 | it "creates admin factories correctly" do 17 | expect(FactoryBot.create(:admin)).to be_admin 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # More details are here: https://help.github.com/articles/about-codeowners/ 5 | 6 | # The '*' pattern is global owners. 7 | 8 | # Order is important. The last matching pattern has the most precedence. 9 | # The folders are ordered as follows: 10 | 11 | # In each subsection folders are ordered first by depth, then alphabetically. 12 | # This should make it easy to add new rules without breaking existing ones. 13 | 14 | # Global rule: 15 | * @mike-burns 16 | -------------------------------------------------------------------------------- /lib/factory_bot/strategy/create.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | module Strategy 3 | class Create 4 | def association(runner) 5 | runner.run 6 | end 7 | 8 | def result(evaluation) 9 | evaluation.object.tap do |instance| 10 | evaluation.notify(:after_build, instance) 11 | evaluation.notify(:before_create, instance) 12 | evaluation.create(instance) 13 | evaluation.notify(:after_create, instance) 14 | end 15 | end 16 | 17 | def to_sym 18 | :create 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/factory_bot/decorator/invocation_tracker.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Decorator 3 | class InvocationTracker < Decorator 4 | def initialize(component) 5 | super 6 | @invoked_methods = [] 7 | end 8 | 9 | def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing 10 | @invoked_methods << name 11 | super 12 | end 13 | ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true) 14 | 15 | def __invoked_methods__ 16 | @invoked_methods.uniq 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/factory_bot/null_factory.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | class NullFactory 4 | attr_reader :definition 5 | 6 | def initialize 7 | @definition = Definition.new(:null_factory) 8 | end 9 | 10 | delegate :defined_traits, :callbacks, :attributes, :constructor, 11 | :to_create, to: :definition 12 | 13 | def compile 14 | end 15 | 16 | def class_name 17 | end 18 | 19 | def evaluator_class 20 | FactoryBot::Evaluator 21 | end 22 | 23 | def hierarchy_class 24 | FactoryBot::DefinitionHierarchy 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /docs/src/sequences/inline-sequences.md: -------------------------------------------------------------------------------- 1 | # Inline sequences 2 | 3 | And it's also possible to define an in-line sequence that is only used in 4 | a particular factory: 5 | 6 | ```ruby 7 | factory :user do 8 | sequence(:email) { |n| "person#{n}@example.com" } 9 | end 10 | ``` 11 | 12 | With Ruby 2.7's support for [numbered parameters][], inline definitions can be 13 | even more abbreviated: 14 | 15 | ```ruby 16 | factory :user do 17 | sequence(:email) { "person#{_1}@example.com" } 18 | end 19 | ``` 20 | 21 | [numbered parameters]: https://ruby-doc.org/core-2.7.1/Proc.html#class-Proc-label-Numbered+parameters 22 | -------------------------------------------------------------------------------- /lib/factory_bot/attribute.rb: -------------------------------------------------------------------------------- 1 | require "factory_bot/attribute/dynamic" 2 | require "factory_bot/attribute/association" 3 | require "factory_bot/attribute/sequence" 4 | 5 | module FactoryBot 6 | # @api private 7 | class Attribute 8 | attr_reader :name, :ignored 9 | 10 | def initialize(name, ignored) 11 | @name = name.to_sym 12 | @ignored = ignored 13 | end 14 | 15 | def to_proc 16 | -> {} 17 | end 18 | 19 | def association? 20 | false 21 | end 22 | 23 | def alias_for?(attr) 24 | FactoryBot.aliases_for(attr).include?(name) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/factory_bot/strategy_calculator.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | class StrategyCalculator 4 | def initialize(name_or_object) 5 | @name_or_object = name_or_object 6 | end 7 | 8 | def strategy 9 | if strategy_is_object? 10 | @name_or_object 11 | else 12 | strategy_name_to_object 13 | end 14 | end 15 | 16 | private 17 | 18 | def strategy_is_object? 19 | @name_or_object.is_a?(Class) 20 | end 21 | 22 | def strategy_name_to_object 23 | FactoryBot::Internal.strategy_by_name(@name_or_object) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/factory_bot/decorator.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Decorator < BasicObject 3 | undef_method :== 4 | 5 | def initialize(component) 6 | @component = component 7 | end 8 | 9 | def method_missing(...) # rubocop:disable Style/MethodMissingSuper 10 | @component.send(...) 11 | end 12 | 13 | def send(...) 14 | __send__(...) 15 | end 16 | 17 | def respond_to_missing?(name, include_private = false) 18 | @component.respond_to?(name, true) || super 19 | end 20 | 21 | def self.const_missing(name) 22 | ::Object.const_get(name) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/factory_bot/evaluator_class_definer.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | class EvaluatorClassDefiner 4 | def initialize(attributes, parent_class) 5 | @parent_class = parent_class 6 | @attributes = attributes 7 | 8 | attributes.each do |attribute| 9 | evaluator_class.define_attribute(attribute.name, &attribute.to_proc) 10 | end 11 | end 12 | 13 | def evaluator_class 14 | @evaluator_class ||= Class.new(@parent_class).tap do |klass| 15 | klass.attribute_lists ||= [] 16 | klass.attribute_lists += [@attributes] 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/acceptance/defining_methods_inside_a_factory_spec.rb: -------------------------------------------------------------------------------- 1 | describe "defining methods inside FactoryBot" do 2 | it "raises with a meaningful message" do 3 | define_model("User") 4 | 5 | bad_factory_definition = -> do 6 | FactoryBot.define do 7 | factory :user do 8 | def generate_name 9 | "John Doe" 10 | end 11 | end 12 | end 13 | end 14 | 15 | expect(&bad_factory_definition).to raise_error( 16 | FactoryBot::MethodDefinitionError, 17 | /Defining methods in blocks \(trait or factory\) is not supported \(generate_name\)/ 18 | ) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /docs/src/associations/overriding-attributes.md: -------------------------------------------------------------------------------- 1 | # Overriding attributes 2 | 3 | You can also override attributes on associations. 4 | 5 | Implicitly: 6 | 7 | ```ruby 8 | factory :post do 9 | # ... 10 | author factory: :author, last_name: "Writely" 11 | end 12 | ``` 13 | 14 | Explicitly: 15 | 16 | 17 | ```ruby 18 | factory :post do 19 | # ... 20 | association :author, last_name: "Writely" 21 | end 22 | ``` 23 | 24 | Or inline using attributes from the factory: 25 | 26 | ```rb 27 | factory :post do 28 | # ... 29 | author_last_name { "Writely" } 30 | author { association :author, last_name: author_last_name } 31 | end 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/src/ref/modify.md: -------------------------------------------------------------------------------- 1 | # FactoryBot.modify 2 | 3 | The `FactoryBot.modify` class method defines a block with an _overriding_ 4 | `factory` method available. That is the only method you can call within the 5 | block. 6 | 7 | The `factory` method within this block takes a mandatory factory name, and a 8 | block. All other arguments are ignored. The factory name must already be 9 | defined. The block is a normal [factory definition block](factory.html). Take 10 | note that [hooks](hooks.html) cannot be cleared and continue to compound. 11 | 12 | For details on why you'd want to use this, see [the 13 | guide](/modifying-factories/summary.html). 14 | -------------------------------------------------------------------------------- /lib/factory_bot/attribute/dynamic.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Attribute 3 | # @api private 4 | class Dynamic < Attribute 5 | def initialize(name, ignored, block) 6 | super(name, ignored) 7 | @block = block 8 | end 9 | 10 | def to_proc 11 | block = @block 12 | 13 | -> { 14 | value = case block.arity 15 | when 1, -1, -2 then instance_exec(self, &block) 16 | else instance_exec(&block) 17 | end 18 | raise SequenceAbuseError if FactoryBot::Sequence === value 19 | 20 | value 21 | } 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/support/matchers/raise_did_you_mean_error.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :raise_did_you_mean_error do 2 | supports_block_expectations 3 | 4 | match do |actual| 5 | # detailed_message introduced in Ruby 3.2 for cleaner integration with 6 | # did_you_mean. See https://bugs.ruby-lang.org/issues/18564 7 | matcher = if KeyError.method_defined?(:detailed_message) 8 | raise_error( 9 | an_instance_of(KeyError) 10 | .and(having_attributes(detailed_message: /Did you mean\?/)) 11 | ) 12 | else 13 | raise_error(KeyError, /Did you mean\?/) 14 | end 15 | 16 | expect(&actual).to matcher 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /docs/src/transient-attributes/with-callbacks.md: -------------------------------------------------------------------------------- 1 | # With callbacks 2 | 3 | If you need to access the evaluated definition itself in a factory\_bot callback, you'll 4 | need to declare a second block argument (for the definition) and access transient 5 | attributes from there. This represents the final, evaluated value. 6 | 7 | ```ruby 8 | factory :user do 9 | transient do 10 | upcased { false } 11 | end 12 | 13 | name { "John Doe" } 14 | 15 | after(:create) do |user, context| 16 | user.name.upcase! if context.upcased 17 | end 18 | end 19 | 20 | create(:user).name 21 | #=> "John Doe" 22 | 23 | create(:user, upcased: true).name 24 | #=> "JOHN DOE" 25 | ``` 26 | -------------------------------------------------------------------------------- /lib/factory_bot/declaration/dynamic.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Declaration 3 | # @api private 4 | class Dynamic < Declaration 5 | def initialize(name, ignored = false, block = nil) 6 | super(name, ignored) 7 | @block = block 8 | end 9 | 10 | def ==(other) 11 | self.class == other.class && 12 | name == other.name && 13 | ignored == other.ignored && 14 | block == other.block 15 | end 16 | 17 | protected 18 | 19 | attr_reader :block 20 | 21 | private 22 | 23 | def build 24 | [Attribute::Dynamic.new(name, @ignored, @block)] 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /docs/src/cookbook/polymorphic-associations.md: -------------------------------------------------------------------------------- 1 | # Polymorphic associations 2 | 3 | Polymorphic associations can be handled with traits: 4 | 5 | ```ruby 6 | FactoryBot.define do 7 | factory :video 8 | factory :photo 9 | 10 | factory :comment do 11 | for_photo # default to the :for_photo trait if none is specified 12 | 13 | trait :for_video do 14 | association :commentable, factory: :video 15 | end 16 | 17 | trait :for_photo do 18 | association :commentable, factory: :photo 19 | end 20 | end 21 | end 22 | ``` 23 | 24 | This allows us to do: 25 | 26 | ```ruby 27 | create(:comment) 28 | create(:comment, :for_video) 29 | create(:comment, :for_photo) 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/src/ref/association.md: -------------------------------------------------------------------------------- 1 | # association 2 | 3 | Within a factory block, use the `association` method to always make an 4 | additional object alongside this one. This name best makes sense within the 5 | context of ActiveRecord. 6 | 7 | The `association` method takes a mandatory name and optional options. 8 | 9 | The options are zero or more trait names (Symbols), followed by a hash 10 | of attribute overrides. When constructing this association, factory\_bot uses 11 | the trait and attribute overrides given. 12 | 13 | See [method_missing](method_missing.html) for a shorthand. See [build 14 | strategies](build-strategies.html) for an explanation of how each build 15 | strategy handles associations. 16 | -------------------------------------------------------------------------------- /lib/factory_bot/evaluation.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Evaluation 3 | def initialize(evaluator, attribute_assigner, to_create, observer) 4 | @evaluator = evaluator 5 | @attribute_assigner = attribute_assigner 6 | @to_create = to_create 7 | @observer = observer 8 | end 9 | 10 | delegate :object, :hash, to: :@attribute_assigner 11 | 12 | def create(result_instance) 13 | case @to_create.arity 14 | when 2 then @to_create[result_instance, @evaluator] 15 | else @to_create[result_instance] 16 | end 17 | end 18 | 19 | def notify(name, result_instance) 20 | @observer.update(name, result_instance) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /docs/src/ref/find_definitions.md: -------------------------------------------------------------------------------- 1 | # FactoryBot.find_definitions 2 | 3 | The `FactoryBot.find_definitions` method loads in all factory\_bot definitions 4 | across the project. 5 | 6 | The load order is controlled by the `FactoryBot.definition_file_paths` 7 | attribute. The default load order is: 8 | 9 | 1. `factories.rb` 10 | 1. `test/factories.rb` 11 | 1. `test/factories/**/*.rb` 12 | 1. `spec/factories.rb` 13 | 1. `spec/factories/**/*.rb` 14 | 15 | ## Rails 16 | 17 | The `.find_definitions` method is called automatically by `factory_bot_rails` 18 | after initialize. The `.definition_file_paths` can be set during initialization 19 | (e.g. `config/initializers`), or via 20 | `Rails.application.config.factory_bot.definition_file_paths`. 21 | -------------------------------------------------------------------------------- /lib/factory_bot/attribute/association.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Attribute 3 | # @api private 4 | class Association < Attribute 5 | attr_reader :factory 6 | 7 | def initialize(name, factory, overrides) 8 | super(name, false) 9 | @factory = factory 10 | @overrides = overrides 11 | end 12 | 13 | def to_proc 14 | factory = @factory 15 | overrides = @overrides 16 | traits_and_overrides = [factory, overrides].flatten 17 | factory_name = traits_and_overrides.shift 18 | 19 | -> { association(factory_name, *traits_and_overrides) } 20 | end 21 | 22 | def association? 23 | true 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /docs/src/ref/lint.md: -------------------------------------------------------------------------------- 1 | # FactoryBot.lint 2 | 3 | The `FactoryBot.lint` method tries each factory and raises 4 | `FactoryBot::InvalidFactoryError` on failure. 5 | 6 | It can take the following optional arguments: 7 | 8 | - A splat of factory names. This will restrict the linting to just the ones listed. The default is all. 9 | - `:strategy` - the [build strategy] to use. The default is `:create`. 10 | - `:traits` - whether to try building each trait, too. The default is `false`. 11 | - `:verbose` - whether to show a stack trace on error. The default is `false`. 12 | 13 | [build strategy]: build-strategies.html 14 | 15 | Suggested techniques for hooking `.lint` into your system is discussed in [the 16 | guide](/linting-factories/summary.html). 17 | -------------------------------------------------------------------------------- /lib/factory_bot/enum.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | class Enum 4 | def initialize(attribute_name, values = nil) 5 | @attribute_name = attribute_name 6 | @values = values 7 | end 8 | 9 | def build_traits(klass) 10 | enum_values(klass).map do |trait_name, value| 11 | build_trait(trait_name, @attribute_name, value || trait_name) 12 | end 13 | end 14 | 15 | private 16 | 17 | def enum_values(klass) 18 | @values || klass.send(@attribute_name.to_s.pluralize) 19 | end 20 | 21 | def build_trait(trait_name, attribute_name, value) 22 | Trait.new(trait_name) do 23 | add_attribute(attribute_name) { value } 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/acceptance/attributes_for_destructuring.rb: -------------------------------------------------------------------------------- 1 | describe "Ruby 3.0: attributes_for destructuring syntax" do 2 | include FactoryBot::Syntax::Methods 3 | 4 | before do 5 | define_model("User", name: :string) 6 | 7 | FactoryBot.define do 8 | factory :user do 9 | sequence(:email) { "email_#{_1}@example.com" } 10 | name { "John Doe" } 11 | end 12 | end 13 | end 14 | 15 | it "supports being destructured" do 16 | # rubocop:disable Lint/Syntax 17 | attributes_for(:user) => {name:, **attributes} 18 | # rubocop:enable Lint/Syntax 19 | 20 | expect(name).to eq("John Doe") 21 | expect(attributes.keys).to eq([:email]) 22 | expect(attributes.fetch(:email)).to match(/email_\d+@example.com/) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /docs/src/callbacks/summary.md: -------------------------------------------------------------------------------- 1 | # Callbacks 2 | 3 | factory\_bot makes four callbacks available: 4 | 5 | * after(:build) - called after a factory is built (via `FactoryBot.build`, `FactoryBot.create`) 6 | * before(:create) - called before a factory is saved (via `FactoryBot.create`) 7 | * after(:create) - called after a factory is saved (via `FactoryBot.create`) 8 | * after(:stub) - called after a factory is stubbed (via `FactoryBot.build_stubbed`) 9 | 10 | Examples: 11 | 12 | ```ruby 13 | # Define a factory that calls the generate_hashed_password method after the user factory is built 14 | factory :user do 15 | after(:build) { |user, context| generate_hashed_password(user) } 16 | end 17 | ``` 18 | 19 | Note that you'll have an instance of the object in the block. 20 | -------------------------------------------------------------------------------- /lib/factory_bot/callback.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Callback 3 | attr_reader :name 4 | 5 | def initialize(name, block) 6 | @name = name.to_sym 7 | @block = block 8 | end 9 | 10 | def run(instance, evaluator) 11 | case block.arity 12 | when 1, -1, -2 then syntax_runner.instance_exec(instance, &block) 13 | when 2 then syntax_runner.instance_exec(instance, evaluator, &block) 14 | else syntax_runner.instance_exec(&block) 15 | end 16 | end 17 | 18 | def ==(other) 19 | name == other.name && 20 | block == other.block 21 | end 22 | 23 | protected 24 | 25 | attr_reader :block 26 | 27 | private 28 | 29 | def syntax_runner 30 | @syntax_runner ||= SyntaxRunner.new 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /docs/src/associations/association-overrides.md: -------------------------------------------------------------------------------- 1 | # Association overrides 2 | 3 | Attribute overrides can be used to link associated objects: 4 | 5 | ```ruby 6 | FactoryBot.define do 7 | factory :author do 8 | name { 'Taylor' } 9 | end 10 | 11 | factory :post do 12 | author 13 | end 14 | end 15 | 16 | eunji = build(:author, name: 'Eunji') 17 | post = build(:post, author: eunji) 18 | ``` 19 | 20 | Ruby 3.1's support for [omitting values][] from `Hash` literals dovetails with 21 | attribute overrides, and provides an opportunity to limit the repetition of 22 | variable names: 23 | 24 | ```ruby 25 | author = build(:author, name: 'Eunji') 26 | 27 | post = build(:post, author:) 28 | ``` 29 | 30 | [omitting values]: https://docs.ruby-lang.org/en/3.1/syntax/literals_rdoc.html#label-Hash+Literals 31 | -------------------------------------------------------------------------------- /docs/src/callbacks/default-callbacks.md: -------------------------------------------------------------------------------- 1 | # Default callbacks 2 | 3 | factory\_bot makes available four callbacks for injecting some code: 4 | 5 | * after(:build) - called after a factory is built (via `FactoryBot.build`, `FactoryBot.create`) 6 | * before(:create) - called before a factory is saved (via `FactoryBot.create`) 7 | * after(:create) - called after a factory is saved (via `FactoryBot.create`) 8 | * after(:stub) - called after a factory is stubbed (via `FactoryBot.build_stubbed`) 9 | 10 | Examples: 11 | 12 | ```ruby 13 | # Define a factory that calls the generate_hashed_password method after it is built 14 | factory :user do 15 | after(:build) { |user| generate_hashed_password(user) } 16 | end 17 | ``` 18 | 19 | Note that you'll have an instance of the object in the block. This can be useful. 20 | -------------------------------------------------------------------------------- /lib/factory_bot/trait.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | class Trait 4 | attr_reader :name, :definition 5 | 6 | def initialize(name, &block) 7 | @name = name.to_s 8 | @block = block 9 | @definition = Definition.new(@name) 10 | proxy = FactoryBot::DefinitionProxy.new(@definition) 11 | 12 | if block 13 | proxy.instance_eval(&@block) 14 | end 15 | end 16 | 17 | delegate :add_callback, :declare_attribute, :to_create, :define_trait, :constructor, 18 | :callbacks, :attributes, :klass, :klass=, to: :@definition 19 | 20 | def names 21 | [@name] 22 | end 23 | 24 | def ==(other) 25 | name == other.name && 26 | block == other.block 27 | end 28 | 29 | protected 30 | 31 | attr_reader :block 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'feature' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 12 | 13 | ### Problem this feature will solve 14 | 15 | 17 | 18 | ### Desired solution 19 | 20 | 21 | 22 | ## Alternatives considered 23 | 24 | 25 | 26 | ## Additional context 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/src/ref/add_attribute.md: -------------------------------------------------------------------------------- 1 | # add_attribute 2 | 3 | Within a factory definition, the `add_attribute` method defines a key/value 4 | pair that will be set when the object is built. 5 | 6 | The `add_attribute` method takes two arguments: a name (Symbol or String) and a 7 | block. This block is called each time this object is constructed. The block is 8 | not called when the attribute is overriden by a build strategy. 9 | 10 | Assignment is done by calling the Ruby attribute setter. For example, given 11 | 12 | ```ruby 13 | FactoryBot.define do 14 | factory :user do 15 | add_attribute(:name) { "Acid Burn" } 16 | end 17 | end 18 | ``` 19 | 20 | This will use the `#name=` setter: 21 | 22 | ```ruby 23 | user = User.new 24 | user.name = "Acid Burn" 25 | ``` 26 | 27 | Also see [method_missing](method_missing.html) for a shorthand. 28 | -------------------------------------------------------------------------------- /docs/src/using-factories/attribute-overrides.md: -------------------------------------------------------------------------------- 1 | # Attribute overrides 2 | 3 | No matter which strategy is used, it's possible to override the defined 4 | attributes by passing a hash: 5 | 6 | ```ruby 7 | # Build a User instance and override the first_name property 8 | user = build(:user, first_name: "Joe") 9 | user.first_name 10 | # => "Joe" 11 | ``` 12 | 13 | Ruby 3.1's support for [omitting values][] from `Hash` literals dovetails with 14 | attribute overrides and provides an opportunity to limit the repetition of 15 | variable names: 16 | 17 | ```ruby 18 | first_name = "Joe" 19 | 20 | # Build a User instance and override the first_name property 21 | user = build(:user, first_name:) 22 | user.first_name 23 | # => "Joe" 24 | ``` 25 | 26 | [omitting values]: https://docs.ruby-lang.org/en/3.1/syntax/literals_rdoc.html#label-Hash+Literals 27 | -------------------------------------------------------------------------------- /NAME.md: -------------------------------------------------------------------------------- 1 | # Project Naming History 2 | 3 | ## Factory Girl 4 | 5 | This library was [initially released](https://robots.thoughtbot.com/waiting-for-a-factory-girl) 6 | in 2008 with the name "Factory Girl". 7 | 8 | We chose the name as a nod in the direction of the [Factory method](https://en.wikipedia.org/wiki/Factory_method_pattern) 9 | and [Object Mother](http://martinfowler.com/bliki/ObjectMother.html) software 10 | patterns from the _Design Patterns_ book, and as a reference to the 11 | [Rolling Stones song](https://www.youtube.com/watch?v=4jKix2DFlnA) of the same 12 | name. 13 | 14 | ## Factory Bot 15 | 16 | The name "Factory Girl" was confusing to some developers who encountered this 17 | library, and offensive or problematic to others. In October 2017 we [renamed the library](https://robots.thoughtbot.com/factory_bot) 18 | to "Factory Bot". 19 | -------------------------------------------------------------------------------- /spec/factory_bot/decorator/attribute_hash_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Decorator::AttributeHash do 2 | describe "#attributes" do 3 | it "returns a hash of attributes" do 4 | attributes = {attribute_1: :value, attribute_2: :value} 5 | component = double(:component, attributes) 6 | 7 | decorator = described_class.new(component, [:attribute_1, :attribute_2]) 8 | 9 | expect(decorator.attributes).to eq(attributes) 10 | end 11 | 12 | context "with an attribute called 'attributes'" do 13 | it "does not call itself recursively" do 14 | attributes = {attributes: :value} 15 | component = double(:component, attributes) 16 | 17 | decorator = described_class.new(component, [:attributes]) 18 | 19 | expect(decorator.attributes).to eq(attributes) 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/acceptance/create_pair_spec.rb: -------------------------------------------------------------------------------- 1 | describe "create multiple instances" do 2 | before do 3 | define_model("Post", title: :string, position: :integer) 4 | 5 | FactoryBot.define do 6 | factory(:post) do |post| 7 | post.title { "Through the Looking Glass" } 8 | post.position { rand(10**4) } 9 | end 10 | end 11 | end 12 | 13 | context "without default attributes" do 14 | subject { FactoryBot.create_pair(:post) } 15 | 16 | its(:length) { should eq 2 } 17 | 18 | it "creates all the posts" do 19 | subject.each do |record| 20 | expect(record).not_to be_new_record 21 | end 22 | end 23 | 24 | it "uses the default factory values" do 25 | subject.each do |record| 26 | expect(record.title).to eq "Through the Looking Glass" 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /docs/src/ref/register_strategy.md: -------------------------------------------------------------------------------- 1 | # FactoryBot.register_strategy 2 | 3 | The `FactoryBot.register_strategy` method is how to add a [build 4 | strategy](build-strategies.html). 5 | 6 | It takes two mandatory arguments: name and class. The name is a Symbol, and 7 | registering it exposes a method under `FactoryBot::Syntax::Methods`. 8 | 9 | The class must define the methods `association` and `result`. 10 | 11 | The `association` method takes an instance of `FactoryRunner`. You can `#run` 12 | this runner, passing a strategy name (it defaults to the current one) and an 13 | optional block. The block is called after the association is built, and is 14 | passed the object that was built. 15 | 16 | The `result` method takes the object that was built for this factory (using 17 | `initalize_with`), and returns the result of this factory for this build 18 | strategy. 19 | -------------------------------------------------------------------------------- /docs/src/traits/attribute-precedence.md: -------------------------------------------------------------------------------- 1 | # Attribute precedence 2 | 3 | Traits that define the same attributes won't raise AttributeDefinitionErrors; 4 | the trait that defines the attribute last gets precedence. 5 | 6 | ```ruby 7 | factory :user do 8 | name { "Friendly User" } 9 | login { name } 10 | 11 | trait :active do 12 | name { "John Doe" } 13 | status { :active } 14 | login { "#{name} (active)" } 15 | end 16 | 17 | trait :inactive do 18 | name { "Jane Doe" } 19 | status { :inactive } 20 | login { "#{name} (inactive)" } 21 | end 22 | 23 | trait :admin do 24 | admin { true } 25 | login { "admin-#{name}" } 26 | end 27 | 28 | factory :active_admin, traits: [:active, :admin] # login will be "admin-John Doe" 29 | factory :inactive_admin, traits: [:admin, :inactive] # login will be "Jane Doe (inactive)" 30 | end 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/src/sequences/aliases.md: -------------------------------------------------------------------------------- 1 | # Aliases 2 | 3 | Sequences can also have aliases. The sequence aliases share the same counter: 4 | 5 | ```ruby 6 | factory :user do 7 | sequence(:email, 1000, aliases: [:sender, :receiver]) { |n| "person#{n}@example.com" } 8 | end 9 | 10 | # will increase value counter for :email which is shared by :sender and :receiver 11 | generate(:sender) 12 | ``` 13 | 14 | Define aliases and use default value (1) for the counter 15 | 16 | ```ruby 17 | factory :user do 18 | sequence(:email, aliases: [:sender, :receiver]) { |n| "person#{n}@example.com" } 19 | end 20 | ``` 21 | 22 | Setting the value: 23 | 24 | ```ruby 25 | factory :user do 26 | sequence(:email, 'a', aliases: [:sender, :receiver]) { |n| "person#{n}@example.com" } 27 | end 28 | ``` 29 | 30 | The value needs to support the `#next` method. Here the next value will be 'a', 31 | then 'b', etc. 32 | -------------------------------------------------------------------------------- /docs/src/custom-methods-to-persist-objects/summary.md: -------------------------------------------------------------------------------- 1 | # Custom Methods to Persist Objects 2 | 3 | By default, creating a record will call `save!` on the instance; since this may 4 | not always be ideal, you can override that behavior by defining `to_create` on 5 | the factory: 6 | 7 | ```ruby 8 | factory :different_orm_model do 9 | to_create { |instance| instance.persist! } 10 | end 11 | ``` 12 | 13 | To disable the persistence method altogether on create, you can `skip_create` 14 | for that factory: 15 | 16 | ```ruby 17 | factory :user_without_database do 18 | skip_create 19 | end 20 | ``` 21 | 22 | To override `to_create` for all factories, define it within the 23 | `FactoryBot.define` block: 24 | 25 | ```ruby 26 | FactoryBot.define do 27 | to_create { |instance| instance.persist! } 28 | 29 | 30 | factory :user do 31 | name { "John Doe" } 32 | end 33 | end 34 | ``` 35 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise "6.1" do 2 | gem "activerecord", "~> 6.1.0" 3 | gem "activerecord-jdbcsqlite3-adapter", "~> 61.0", platforms: [:jruby] 4 | gem "sqlite3", platforms: [:ruby] 5 | end 6 | 7 | appraise "7.0" do 8 | gem "activerecord", "~> 7.0.0" 9 | gem "activerecord-jdbcsqlite3-adapter", "~> 70.0", platforms: [:jruby] 10 | gem "sqlite3", platforms: [:ruby] 11 | end 12 | 13 | appraise "7.1" do 14 | gem "activerecord", "~> 7.1.0" 15 | # When version 71 is released, uncomment this and also allow it in the GitHub 16 | # Action build workflow. 17 | # gem "activerecord-jdbcsqlite3-adapter", "~> 71.0", platforms: [:jruby] 18 | gem "sqlite3", platforms: [:ruby] 19 | end 20 | 21 | appraise "main" do 22 | gem "activerecord", git: "https://github.com/rails/rails.git", branch: "main" 23 | gem "activerecord-jdbcsqlite3-adapter", "~> 70.0", platforms: [:jruby] 24 | gem "sqlite3", platforms: [:ruby] 25 | end 26 | -------------------------------------------------------------------------------- /spec/factory_bot/null_object_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::NullObject do 2 | it "responds to the given methods" do 3 | methods_to_respond_to = %w[id age name admin?] 4 | null_object = FactoryBot::NullObject.new(methods_to_respond_to) 5 | 6 | methods_to_respond_to.each do |method_name| 7 | expect(null_object.__send__(method_name)).to be_nil 8 | expect(null_object).to respond_to(method_name) 9 | end 10 | end 11 | 12 | it "does not respond to other methods" do 13 | methods_to_respond_to = %w[id age name admin?] 14 | methods_to_not_respond_to = %w[email date_of_birth title] 15 | null_object = FactoryBot::NullObject.new(methods_to_respond_to) 16 | 17 | methods_to_not_respond_to.each do |method_name| 18 | expect { null_object.__send__(method_name) }.to raise_error(NoMethodError) 19 | expect(null_object).not_to respond_to(method_name) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /docs/src/traits/with-associations.md: -------------------------------------------------------------------------------- 1 | # With associations 2 | 3 | Traits can be used with associations easily too: 4 | 5 | ```ruby 6 | factory :user do 7 | name { "Friendly User" } 8 | 9 | trait :admin do 10 | admin { true } 11 | end 12 | end 13 | 14 | factory :post do 15 | association :user, :admin, name: 'John Doe' 16 | end 17 | 18 | # creates an admin user with name "John Doe" 19 | create(:post).user 20 | ``` 21 | 22 | When you're using association names that are different than the factory: 23 | 24 | ```ruby 25 | factory :user do 26 | name { "Friendly User" } 27 | 28 | trait :admin do 29 | admin { true } 30 | end 31 | end 32 | 33 | factory :post do 34 | association :author, :admin, factory: :user, name: 'John Doe' 35 | # or 36 | association :author, factory: [:user, :admin], name: 'John Doe' 37 | end 38 | 39 | # creates an admin user with name "John Doe" 40 | create(:post).author 41 | ``` 42 | -------------------------------------------------------------------------------- /lib/factory_bot/find_definitions.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class << self 3 | # An Array of strings specifying locations that should be searched for 4 | # factory definitions. By default, factory_bot will attempt to require 5 | # "factories", "test/factories" and "spec/factories". Only the first 6 | # existing file will be loaded. 7 | attr_accessor :definition_file_paths 8 | end 9 | 10 | self.definition_file_paths = %w[factories test/factories spec/factories] 11 | 12 | def self.find_definitions 13 | absolute_definition_file_paths = definition_file_paths.map { |path| File.expand_path(path) } 14 | 15 | absolute_definition_file_paths.uniq.each do |path| 16 | load("#{path}.rb") if File.exist?("#{path}.rb") 17 | 18 | if File.directory? path 19 | Dir[File.join(path, "**", "*.rb")].sort.each do |file| 20 | load file 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/factory_bot/factory_runner.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class FactoryRunner 3 | def initialize(name, strategy, traits_and_overrides) 4 | @name = name 5 | @strategy = strategy 6 | 7 | @overrides = traits_and_overrides.extract_options! 8 | @traits = traits_and_overrides 9 | end 10 | 11 | def run(runner_strategy = @strategy, &block) 12 | factory = FactoryBot::Internal.factory_by_name(@name) 13 | 14 | factory.compile 15 | 16 | if @traits.any? 17 | factory = factory.with_traits(@traits) 18 | end 19 | 20 | instrumentation_payload = { 21 | name: @name, 22 | strategy: runner_strategy, 23 | traits: @traits, 24 | overrides: @overrides, 25 | factory: factory 26 | } 27 | 28 | ActiveSupport::Notifications.instrument("factory_bot.run_factory", instrumentation_payload) do 29 | factory.run(runner_strategy, @overrides, &block) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /docs/src/using-without-bundler/summary.md: -------------------------------------------------------------------------------- 1 | # Using Without Bundler 2 | 3 | If you're not using Bundler, be sure to have the gem installed and call: 4 | 5 | ```ruby 6 | require 'factory_bot' 7 | ``` 8 | 9 | Once required, assuming you have a directory structure of `spec/factories` or 10 | `test/factories`, all you'll need to do is run: 11 | 12 | ```ruby 13 | FactoryBot.find_definitions 14 | ``` 15 | 16 | If you're using a separate directory structure for your factories, you can 17 | change the definition file paths before trying to find definitions: 18 | 19 | ```ruby 20 | FactoryBot.definition_file_paths = %w(custom_factories_directory) 21 | FactoryBot.find_definitions 22 | ``` 23 | 24 | If you don't have a separate directory of factories and would like to define 25 | them inline, that's possible as well: 26 | 27 | ```ruby 28 | require 'factory_bot' 29 | 30 | FactoryBot.define do 31 | factory :user do 32 | name { 'John Doe' } 33 | date_of_birth { 21.years.ago } 34 | end 35 | end 36 | ``` 37 | -------------------------------------------------------------------------------- /spec/factory_bot/callback_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Callback do 2 | it "has a name" do 3 | expect(FactoryBot::Callback.new(:after_create, -> {}).name).to eq :after_create 4 | end 5 | 6 | it "converts strings to symbols" do 7 | expect(FactoryBot::Callback.new("after_create", -> {}).name).to eq :after_create 8 | end 9 | 10 | it "runs its block with no parameters" do 11 | ran_with = nil 12 | FactoryBot::Callback.new(:after_create, -> { ran_with = [] }).run(:one, :two) 13 | expect(ran_with).to eq [] 14 | end 15 | 16 | it "runs its block with one parameter" do 17 | ran_with = nil 18 | FactoryBot::Callback.new(:after_create, ->(one) { ran_with = [one] }).run(:one, :two) 19 | expect(ran_with).to eq [:one] 20 | end 21 | 22 | it "runs its block with two parameters" do 23 | ran_with = nil 24 | FactoryBot::Callback.new(:after_create, ->(one, two) { ran_with = [one, two] }).run(:one, :two) 25 | expect(ran_with).to eq [:one, :two] 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /docs/src/traits/summary.md: -------------------------------------------------------------------------------- 1 | # Traits 2 | 3 | Traits allow you to group attributes together and then apply them 4 | to any factory. 5 | 6 | ```ruby 7 | factory :user, aliases: [:author] 8 | 9 | factory :story do 10 | title { "My awesome story" } 11 | author 12 | 13 | trait :published do 14 | published { true } 15 | end 16 | 17 | trait :unpublished do 18 | published { false } 19 | end 20 | 21 | trait :week_long_publishing do 22 | start_at { 1.week.ago } 23 | end_at { Time.now } 24 | end 25 | 26 | trait :month_long_publishing do 27 | start_at { 1.month.ago } 28 | end_at { Time.now } 29 | end 30 | 31 | factory :week_long_published_story, traits: [:published, :week_long_publishing] 32 | factory :month_long_published_story, traits: [:published, :month_long_publishing] 33 | factory :week_long_unpublished_story, traits: [:unpublished, :week_long_publishing] 34 | factory :month_long_unpublished_story, traits: [:unpublished, :month_long_publishing] 35 | end 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/src/traits/defining-traits.md: -------------------------------------------------------------------------------- 1 | # Defining traits 2 | 3 | Traits allow you to group attributes together and then apply them 4 | to any factory. 5 | 6 | ```ruby 7 | factory :user, aliases: [:author] 8 | 9 | factory :story do 10 | title { "My awesome story" } 11 | author 12 | 13 | trait :published do 14 | published { true } 15 | end 16 | 17 | trait :unpublished do 18 | published { false } 19 | end 20 | 21 | trait :week_long_publishing do 22 | start_at { 1.week.ago } 23 | end_at { Time.now } 24 | end 25 | 26 | trait :month_long_publishing do 27 | start_at { 1.month.ago } 28 | end_at { Time.now } 29 | end 30 | 31 | factory :week_long_published_story, traits: [:published, :week_long_publishing] 32 | factory :month_long_published_story, traits: [:published, :month_long_publishing] 33 | factory :week_long_unpublished_story, traits: [:unpublished, :week_long_publishing] 34 | factory :month_long_unpublished_story, traits: [:unpublished, :month_long_publishing] 35 | end 36 | ``` 37 | -------------------------------------------------------------------------------- /spec/factory_bot/disallows_duplicates_registry_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Decorator::DisallowsDuplicatesRegistry do 2 | it "delegates #register to the registry when not registered" do 3 | registry = double("registry", name: "Great thing", register: true) 4 | decorator = FactoryBot::Decorator::DisallowsDuplicatesRegistry.new(registry) 5 | allow(registry).to receive(:registered?).and_return false 6 | decorator.register(:awesome, {}) 7 | 8 | expect(registry).to have_received(:register).with(:awesome, {}) 9 | end 10 | 11 | it "raises when attempting to #register a previously registered strategy" do 12 | registry = double("registry", name: "Great thing", register: true) 13 | decorator = FactoryBot::Decorator::DisallowsDuplicatesRegistry.new(registry) 14 | allow(registry).to receive(:registered?).and_return true 15 | 16 | expect { decorator.register(:same_name, {}) } 17 | .to raise_error(FactoryBot::DuplicateDefinitionError, "Great thing already registered: same_name") 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /docs/src/custom-callbacks/summary.md: -------------------------------------------------------------------------------- 1 | # Custom Callbacks 2 | 3 | Custom callbacks can be defined if you're using custom strategies: 4 | 5 | ```ruby 6 | class JsonStrategy 7 | def initialize 8 | @strategy = FactoryBot.strategy_by_name(:create).new 9 | end 10 | 11 | delegate :association, to: :@strategy 12 | 13 | def result(evaluation) 14 | result = @strategy.result(evaluation) 15 | evaluation.notify(:before_json, result) 16 | 17 | result.to_json.tap do |json| 18 | evaluation.notify(:after_json, json) 19 | evaluation.notify(:make_json_awesome, json) 20 | end 21 | end 22 | 23 | def to_sym 24 | :json 25 | end 26 | end 27 | 28 | FactoryBot.register_strategy(:json, JsonStrategy) 29 | 30 | FactoryBot.define do 31 | factory :user do 32 | before(:json) { |user| do_something_to(user) } 33 | after(:json) { |user_json| do_something_to(user_json) } 34 | callback(:make_json_awesome) { |user_json| do_something_to(user_json) } 35 | end 36 | end 37 | ``` 38 | -------------------------------------------------------------------------------- /spec/acceptance/nested_attributes_spec.rb: -------------------------------------------------------------------------------- 1 | describe "association assignment from nested attributes" do 2 | before do 3 | define_model("Post", title: :string) do 4 | has_many :comments 5 | accepts_nested_attributes_for :comments 6 | end 7 | 8 | define_model("Comment", post_id: :integer, body: :text) do 9 | belongs_to :post 10 | end 11 | 12 | FactoryBot.define do 13 | factory :post do 14 | comments_attributes { [FactoryBot.attributes_for(:comment), FactoryBot.attributes_for(:comment)] } 15 | end 16 | 17 | factory :comment do 18 | sequence(:body) { |n| "Body #{n}" } 19 | end 20 | end 21 | end 22 | 23 | it "assigns the correct amount of comments" do 24 | expect(FactoryBot.create(:post).comments.count).to eq 2 25 | end 26 | 27 | it "assigns the correct amount of comments when overridden" do 28 | post = FactoryBot.create(:post, comments_attributes: [FactoryBot.attributes_for(:comment)]) 29 | 30 | expect(post.comments.count).to eq 1 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/factory_bot/definition_hierarchy.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class DefinitionHierarchy 3 | delegate :callbacks, :constructor, :to_create, to: Internal 4 | 5 | def self.build_from_definition(definition) 6 | build_to_create(&definition.to_create) 7 | build_constructor(&definition.constructor) 8 | add_callbacks definition.callbacks 9 | end 10 | 11 | def self.add_callbacks(callbacks) 12 | if callbacks.any? 13 | define_method :callbacks do 14 | super() + callbacks 15 | end 16 | end 17 | end 18 | private_class_method :add_callbacks 19 | 20 | def self.build_constructor(&block) 21 | if block 22 | define_method(:constructor) do 23 | block 24 | end 25 | end 26 | end 27 | private_class_method :build_constructor 28 | 29 | def self.build_to_create(&block) 30 | if block 31 | define_method(:to_create) do 32 | block 33 | end 34 | end 35 | end 36 | private_class_method :build_to_create 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/factory_bot/strategy_calculator_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::StrategyCalculator do 2 | it "returns the class passed when it is instantiated with a class" do 3 | strategy = define_class("MyAwesomeClass") 4 | calculator = FactoryBot::StrategyCalculator.new(strategy).strategy 5 | 6 | expect(calculator).to eq strategy 7 | end 8 | 9 | it "finds the strategy by name when instantiated with a symbol" do 10 | strategy = define_class("MyAwesomeClass") 11 | allow(FactoryBot::Internal).to receive(:strategy_by_name).and_return(strategy) 12 | FactoryBot::StrategyCalculator.new(:build).strategy 13 | 14 | expect(FactoryBot::Internal).to have_received(:strategy_by_name).with(:build) 15 | end 16 | 17 | it "returns the strategy found when instantiated with a symbol" do 18 | strategy = define_class("MyAwesomeClass") 19 | allow(FactoryBot::Internal).to receive(:strategy_by_name).and_return(strategy) 20 | calculator = FactoryBot::StrategyCalculator.new(:build).strategy 21 | 22 | expect(calculator).to eq strategy 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/factory_bot/configuration.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | class Configuration 4 | attr_reader( 5 | :callback_names, 6 | :factories, 7 | :inline_sequences, 8 | :sequences, 9 | :strategies, 10 | :traits 11 | ) 12 | 13 | def initialize 14 | @factories = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Factory")) 15 | @sequences = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Sequence")) 16 | @traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Trait")) 17 | @strategies = Registry.new("Strategy") 18 | @callback_names = Set.new 19 | @definition = Definition.new(:configuration) 20 | @inline_sequences = [] 21 | 22 | to_create(&:save!) 23 | initialize_with { new } 24 | end 25 | 26 | delegate :to_create, :skip_create, :constructor, :before, :after, 27 | :callback, :callbacks, to: :@definition 28 | 29 | def initialize_with(&block) 30 | @definition.define_constructor(&block) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/factory_bot/aliases_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot, "aliases" do 2 | it "for an attribute should include the original attribute and a version suffixed with '_id'" do 3 | aliases = FactoryBot.aliases_for(:test) 4 | 5 | expect(aliases).to include(:test, :test_id) 6 | end 7 | 8 | it "for a foreign key should include both the suffixed and un-suffixed variants" do 9 | aliases = FactoryBot.aliases_for(:test_id) 10 | 11 | expect(aliases).to include(:test, :test_id) 12 | end 13 | 14 | it "for an attribute which starts with an underscore should not include a non-underscored version" do 15 | aliases = FactoryBot.aliases_for(:_id) 16 | 17 | expect(aliases).not_to include(:id) 18 | end 19 | end 20 | 21 | describe FactoryBot, "after defining an alias" do 22 | it "the list of aliases should include a variant with no suffix at all, and one with an '_id' suffix" do 23 | FactoryBot.aliases << [/(.*)_suffix/, '\1'] 24 | aliases = FactoryBot.aliases_for(:test_suffix) 25 | 26 | expect(aliases).to include(:test, :test_suffix_id) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "rspec" 2 | require "rspec/its" 3 | 4 | require "simplecov" if RUBY_ENGINE == "ruby" 5 | 6 | require "factory_bot" 7 | 8 | if RUBY_ENGINE == "jruby" 9 | # Workaround for issue in I18n/JRuby combo. 10 | # See https://github.com/jruby/jruby/issues/6547 and 11 | # https://github.com/ruby-i18n/i18n/issues/555 12 | require "i18n/backend" 13 | require "i18n/backend/simple" 14 | end 15 | 16 | Dir["spec/support/**/*.rb"].each { |f| require File.expand_path(f) } 17 | 18 | RSpec.configure do |config| 19 | config.mock_with :rspec do |mocks| 20 | # Prevents you from mocking or stubbing a method that does not exist on a 21 | # real object. This is generally recommended, and will default to `true` in 22 | # RSpec 4. 23 | mocks.verify_partial_doubles = true 24 | end 25 | 26 | config.include DeclarationMatchers 27 | 28 | config.before do 29 | FactoryBot.reload 30 | end 31 | 32 | config.order = :random 33 | Kernel.srand config.seed 34 | 35 | config.example_status_persistence_file_path = "tmp/rspec_examples.txt" 36 | end 37 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "bundler" 3 | require "rake" 4 | require "yard" 5 | require "rspec/core/rake_task" 6 | require "cucumber/rake/task" 7 | require "standard/rake" 8 | 9 | Bundler::GemHelper.install_tasks(name: "factory_bot") 10 | 11 | desc "Default: run all specs and standard" 12 | task default: %w[all_specs standard] 13 | 14 | desc "Run all specs and features" 15 | task all_specs: %w[spec:unit spec:acceptance features] 16 | 17 | namespace :spec do 18 | desc "Run unit specs" 19 | RSpec::Core::RakeTask.new("unit") do |t| 20 | t.pattern = "spec/{*_spec.rb,factory_bot/**/*_spec.rb}" 21 | end 22 | 23 | desc "Run acceptance specs" 24 | RSpec::Core::RakeTask.new("acceptance") do |t| 25 | t.pattern = "spec/acceptance/**/*_spec.rb" 26 | end 27 | end 28 | 29 | desc "Run the unit and acceptance specs" 30 | task spec: ["spec:unit", "spec:acceptance"] 31 | 32 | Cucumber::Rake::Task.new(:features) do |t| 33 | t.fork = true 34 | t.cucumber_opts = ["--format", (ENV["CUCUMBER_FORMAT"] || "progress")] 35 | end 36 | 37 | YARD::Rake::YardocTask.new do |t| 38 | end 39 | -------------------------------------------------------------------------------- /docs/src/using-factories/build-strategies.md: -------------------------------------------------------------------------------- 1 | # Build strategies 2 | 3 | factory\_bot supports several different build strategies: `build`, `create`, 4 | `attributes_for` and `build_stubbed`: 5 | 6 | ```ruby 7 | # Returns a User instance that's not saved 8 | user = build(:user) 9 | 10 | # Returns a saved User instance 11 | user = create(:user) 12 | 13 | # Returns a hash of attributes, which can be used to build a User instance for example 14 | attrs = attributes_for(:user) 15 | 16 | # Integrates with Ruby 3.0's support for pattern matching assignment 17 | attributes_for(:user) => {email:, name:, **attrs} 18 | 19 | # Returns an object with all defined attributes stubbed out 20 | stub = build_stubbed(:user) 21 | 22 | # Passing a block to any of the methods above will yield the return object 23 | create(:user) do |user| 24 | user.posts.create(attributes_for(:post)) 25 | end 26 | ``` 27 | 28 | # build_stubbed and Marshal.dump 29 | 30 | Note that objects created with `build_stubbed` cannot be serialized with 31 | `Marshal.dump`, since factory\_bot defines singleton methods on these objects. 32 | -------------------------------------------------------------------------------- /docs/src/setup/summary.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | Installation varies based on the framework you are using, if any, and 4 | optionally the test framework. 5 | 6 | Since installation varies based on code that we do not control, those docs are 7 | kept up-to-date in [our wiki]. We encourage you to edit the wiki as the 8 | frameworks change. 9 | 10 | Below we document the most common setup. However, **we go into more detail in 11 | [our wiki]**. 12 | 13 | [our wiki]: https://github.com/thoughtbot/factory_bot/wiki/Installation 14 | 15 | ## Update Your Gemfile 16 | 17 | If you're using Rails: 18 | 19 | ```ruby 20 | gem "factory_bot_rails" 21 | ``` 22 | 23 | If you're *not* using Rails: 24 | 25 | ```ruby 26 | gem "factory_bot" 27 | ``` 28 | 29 | For more, see [our wiki]. 30 | 31 | ## Configure your test suite 32 | 33 | ### RSpec 34 | 35 | ```ruby 36 | RSpec.configure do |config| 37 | config.include FactoryBot::Syntax::Methods 38 | end 39 | ``` 40 | 41 | ### Test::Unit 42 | 43 | ```ruby 44 | class Test::Unit::TestCase 45 | include FactoryBot::Syntax::Methods 46 | end 47 | ``` 48 | 49 | For more, see [our wiki]. 50 | -------------------------------------------------------------------------------- /docs/src/aliases/summary.md: -------------------------------------------------------------------------------- 1 | # Aliases 2 | 3 | factory\_bot allows you to define aliases to existing factories to make them 4 | easier to re-use. This could come in handy when, for example, your Post object 5 | has an author attribute that actually refers to an instance of a User class. 6 | While normally factory\_bot can infer the factory name from the association name, 7 | in this case it will look for an author factory in vain. So, alias your user 8 | factory so it can be used under alias names. 9 | 10 | ```ruby 11 | factory :user, aliases: [:author, :commenter] do 12 | first_name { "John" } 13 | last_name { "Doe" } 14 | date_of_birth { 18.years.ago } 15 | end 16 | 17 | factory :post do 18 | # The alias allows us to write author instead of 19 | # association :author, factory: :user 20 | author 21 | title { "How to read a book effectively" } 22 | body { "There are five steps involved." } 23 | end 24 | 25 | factory :comment do 26 | # The alias allows us to write commenter instead of 27 | # association :commenter, factory: :user 28 | commenter 29 | body { "Great article!" } 30 | end 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/src/traits/using.md: -------------------------------------------------------------------------------- 1 | # Using traits 2 | 3 | Traits can also be passed in as a list of Symbols when you construct an instance 4 | from factory\_bot. 5 | 6 | ```ruby 7 | factory :user do 8 | name { "Friendly User" } 9 | 10 | trait :active do 11 | name { "John Doe" } 12 | status { :active } 13 | end 14 | 15 | trait :admin do 16 | admin { true } 17 | end 18 | end 19 | 20 | # creates an admin user with :active status and name "Jon Snow" 21 | create(:user, :admin, :active, name: "Jon Snow") 22 | ``` 23 | 24 | This ability works with `build`, `build_stubbed`, `attributes_for`, and `create`. 25 | 26 | `create_list` and `build_list` methods are supported as well. Remember to pass 27 | the number of instances to create/build as second parameter, as documented in 28 | the "Building or Creating Multiple Records" section of this file. 29 | 30 | ```ruby 31 | factory :user do 32 | name { "Friendly User" } 33 | 34 | trait :admin do 35 | admin { true } 36 | end 37 | end 38 | 39 | # creates 3 admin users with :active status and name "Jon Snow" 40 | create_list(:user, 3, :admin, :active, name: "Jon Snow") 41 | ``` 42 | -------------------------------------------------------------------------------- /lib/factory_bot/declaration/implicit.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Declaration 3 | # @api private 4 | class Implicit < Declaration 5 | def initialize(name, factory = nil, ignored = false) 6 | super(name, ignored) 7 | @factory = factory 8 | end 9 | 10 | def ==(other) 11 | self.class == other.class && 12 | name == other.name && 13 | factory == other.factory && 14 | ignored == other.ignored 15 | end 16 | 17 | protected 18 | 19 | attr_reader :factory 20 | 21 | private 22 | 23 | def build 24 | if FactoryBot.factories.registered?(name) 25 | [Attribute::Association.new(name, name, {})] 26 | elsif FactoryBot::Internal.sequences.registered?(name) 27 | [Attribute::Sequence.new(name, name, @ignored)] 28 | elsif @factory.name.to_s == name.to_s 29 | message = "Self-referencing trait '#{@name}'" 30 | raise TraitDefinitionError, message 31 | else 32 | @factory.inherit_traits([name]) 33 | [] 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2019 Joe Ferris and thoughtbot, inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /docs/src/rails-preloaders-and-rspec/summary.md: -------------------------------------------------------------------------------- 1 | # Rails Preloaders and RSpec 2 | 3 | When running RSpec with a Rails preloader such as `spring` or `zeus`, it's 4 | possible to encounter an `ActiveRecord::AssociationTypeMismatch` error when 5 | creating a factory with associations, as below: 6 | 7 | ```ruby 8 | FactoryBot.define do 9 | factory :united_states, class: "Location" do 10 | name { 'United States' } 11 | association :location_group, factory: :north_america 12 | end 13 | 14 | factory :north_america, class: "LocationGroup" do 15 | name { 'North America' } 16 | end 17 | end 18 | ``` 19 | 20 | The error occurs during the run of the test suite: 21 | 22 | ``` 23 | Failure/Error: united_states = create(:united_states) 24 | ActiveRecord::AssociationTypeMismatch: 25 | LocationGroup(#70251250797320) expected, got LocationGroup(#70251200725840) 26 | ``` 27 | 28 | The two possible solutions are to either run the suite without the preloader, 29 | or to add `FactoryBot.reload` to the RSpec configuration, like so: 30 | 31 | ```ruby 32 | RSpec.configure do |config| 33 | config.before(:suite) { FactoryBot.reload } 34 | end 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/src/defining/hash-attributes.md: -------------------------------------------------------------------------------- 1 | # Hash attributes 2 | 3 | Because of the block syntax in Ruby, defining attributes as `Hash`es (for 4 | serialized/JSON columns, for example) requires two sets of curly brackets: 5 | 6 | ```ruby 7 | factory :program do 8 | configuration { { auto_resolve: false, auto_define: true } } 9 | end 10 | ``` 11 | 12 | Alternatively you may prefer `do`/`end` syntax: 13 | 14 | ```ruby 15 | factory :program do 16 | configuration do 17 | { auto_resolve: false, auto_define: true } 18 | end 19 | end 20 | ``` 21 | 22 | --- 23 | 24 | However, defining a value as a hash makes it complicated to set values within 25 | the hash when constructing an object. Instead, prefer to use factory\_bot 26 | itself: 27 | 28 | ```ruby 29 | factory :program do 30 | configuration { attributes_for(:configuration) } 31 | end 32 | 33 | factory :configuration do 34 | auto_resolve { false } 35 | auto_define { true } 36 | end 37 | ``` 38 | 39 | This way you can more easily set value when building: 40 | 41 | ```ruby 42 | create( 43 | :program, 44 | configuration: attributes_for( 45 | :configuration, 46 | auto_resolve: true, 47 | ) 48 | ) 49 | ``` 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 12 | 13 | ### Description 14 | 15 | 16 | 17 | ### Reproduction Steps 18 | 19 | 22 | 23 | 26 | 27 | ### Expected behavior 28 | 29 | 30 | 31 | ### Actual behavior 32 | 33 | 34 | 35 | ### System configuration 36 | **factory_bot version**: 37 | **rails version**: 38 | **ruby version**: 39 | -------------------------------------------------------------------------------- /spec/acceptance/attribute_aliases_spec.rb: -------------------------------------------------------------------------------- 1 | describe "attribute aliases" do 2 | before do 3 | define_model("User", name: :string, age: :integer) 4 | 5 | define_model("Post", user_id: :integer) do 6 | belongs_to :user 7 | end 8 | 9 | FactoryBot.define do 10 | factory :user do 11 | factory :user_with_name do 12 | name { "John Doe" } 13 | end 14 | end 15 | 16 | factory :post do 17 | user 18 | end 19 | 20 | factory :post_with_named_user, class: Post do 21 | user factory: :user_with_name, age: 20 22 | end 23 | end 24 | end 25 | 26 | context "assigning an association by foreign key" do 27 | subject { FactoryBot.build(:post, user_id: 1) } 28 | 29 | it "doesn't assign both an association and its foreign key" do 30 | expect(subject.user_id).to eq 1 31 | end 32 | end 33 | 34 | context "assigning an association by passing factory" do 35 | subject { FactoryBot.create(:post_with_named_user).user } 36 | 37 | it "assigns attributes correctly" do 38 | expect(subject.name).to eq "John Doe" 39 | expect(subject.age).to eq 20 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write # To push a branch 12 | pull-requests: write # To create a PR from that branch 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - name: Install mdbook 18 | run: | 19 | mkdir mdbook 20 | curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.27/mdbook-v0.4.27-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook 21 | echo `pwd`/mdbook >> $GITHUB_PATH 22 | - name: Deploy GitHub Pages 23 | run: | 24 | cd docs 25 | mdbook build 26 | git worktree add gh-pages 27 | git config user.name "Deploy from CI" 28 | git config user.email "" 29 | cd gh-pages 30 | # Delete the ref to avoid keeping history. 31 | git update-ref -d refs/heads/gh-pages 32 | rm -rf * 33 | mv ../book/* . 34 | git add . 35 | git commit -m "Deploy $GITHUB_SHA to gh-pages" 36 | git push --force --set-upstream origin gh-pages 37 | -------------------------------------------------------------------------------- /spec/acceptance/add_attribute_spec.rb: -------------------------------------------------------------------------------- 1 | describe "#add_attribute" do 2 | it "assigns attributes for reserved words on .build" do 3 | define_model("Post", title: :string, sequence: :string, new: :boolean) 4 | 5 | FactoryBot.define do 6 | factory :post do 7 | add_attribute(:title) { "Title" } 8 | add_attribute(:sequence) { "Sequence" } 9 | add_attribute(:new) { true } 10 | end 11 | end 12 | 13 | post = FactoryBot.build(:post) 14 | 15 | expect(post.title).to eq "Title" 16 | expect(post.sequence).to eq "Sequence" 17 | expect(post.new).to eq true 18 | end 19 | 20 | it "assigns attributes for reserved words on .attributes_for" do 21 | define_model("Post", title: :string, sequence: :string, new: :boolean) 22 | 23 | FactoryBot.define do 24 | factory :post do 25 | add_attribute(:title) { "Title" } 26 | add_attribute(:sequence) { "Sequence" } 27 | add_attribute(:new) { true } 28 | end 29 | end 30 | 31 | post = FactoryBot.attributes_for(:post) 32 | 33 | expect(post[:title]).to eq "Title" 34 | expect(post[:sequence]).to eq "Sequence" 35 | expect(post[:new]).to eq true 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /features/step_definitions/factory_bot_steps.rb: -------------------------------------------------------------------------------- 1 | module FactoryBotDefinitionsHelper 2 | def append_file_to_factory_bot_definitions_path(path_to_file) 3 | FactoryBot.definition_file_paths ||= [] 4 | FactoryBot.definition_file_paths << path_to_file 5 | end 6 | end 7 | 8 | World(FactoryBotDefinitionsHelper) 9 | 10 | When(/^"([^"]*)" is added to FactoryBot's file definitions path$/) do |file_name| 11 | new_factory_file = File.join(expand_path("."), file_name.gsub(".rb", "")) 12 | 13 | append_file_to_factory_bot_definitions_path(new_factory_file) 14 | 15 | step %(I find definitions) 16 | end 17 | 18 | When(/^"([^"]*)" is added to FactoryBot's file definitions path as an absolute path$/) do |file_name| 19 | new_factory_file = File.expand_path(File.join(expand_path("."), file_name.gsub(".rb", ""))) 20 | 21 | append_file_to_factory_bot_definitions_path(new_factory_file) 22 | 23 | step %(I find definitions) 24 | end 25 | 26 | When(/^I create a "([^"]*)" instance from FactoryBot$/) do |factory_name| 27 | FactoryBot.create(factory_name) 28 | end 29 | 30 | When(/^I find definitions$/) do 31 | FactoryBot.find_definitions 32 | end 33 | 34 | When(/^I reload factories$/) do 35 | FactoryBot.reload 36 | end 37 | -------------------------------------------------------------------------------- /lib/factory_bot/declaration_list.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | class DeclarationList 4 | include Enumerable 5 | 6 | def initialize(name = nil) 7 | @declarations = [] 8 | @name = name 9 | @overridable = false 10 | end 11 | 12 | def declare_attribute(declaration) 13 | delete_declaration(declaration) if overridable? 14 | 15 | @declarations << declaration 16 | declaration 17 | end 18 | 19 | def overridable 20 | @overridable = true 21 | end 22 | 23 | def attributes 24 | @attributes ||= AttributeList.new(@name).tap do |list| 25 | to_attributes.each do |attribute| 26 | list.define_attribute(attribute) 27 | end 28 | end 29 | end 30 | 31 | def each(&block) 32 | @declarations.each(&block) 33 | end 34 | 35 | private 36 | 37 | def delete_declaration(declaration) 38 | @declarations.delete_if { |decl| decl.name == declaration.name } 39 | end 40 | 41 | def to_attributes 42 | @declarations.reduce([]) { |result, declaration| result + declaration.to_attributes } 43 | end 44 | 45 | def overridable? 46 | @overridable 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | 1. Update version file accordingly and run `bundle install` to update the 4 | Gemfile.lock and `bundle exec appraisal install` to update the Appraisal 5 | gemfile.lock files. 6 | 1. Update `NEWS.md` to reflect the changes since last release. 7 | 1. Commit changes. 8 | There shouldn't be code changes, 9 | and thus CI doesn't need to run, 10 | so you can add "[ci skip]" to the commit message. 11 | 1. Tag the release: `git tag -s vVERSION` 12 | - We recommend the [_quick guide on how to sign a release_] from git ready. 13 | 1. Push changes: `git push && git push --tags` 14 | 1. Build and publish: 15 | ```bash 16 | gem build factory_bot.gemspec 17 | gem push factory_bot-VERSION.gem 18 | ``` 19 | 1. Add a new GitHub release using the recent `NEWS.md` as the content. Sample 20 | URL: https://github.com/thoughtbot/factory_bot/releases/new?tag=vVERSION 21 | 1. Announce the new release, 22 | making sure to say "thank you" to the contributors 23 | who helped shape this version! 24 | thoughtbotters can refer to the handbook for announcements guidelines. 25 | 26 | [_quick guide on how to sign a release_]: http://gitready.com/advanced/2014/11/02/gpg-sign-releases.html 27 | -------------------------------------------------------------------------------- /docs/src/callbacks/multiple-callbacks.md: -------------------------------------------------------------------------------- 1 | # Multiple callbacks 2 | 3 | You can also define multiple types of callbacks on the same factory: 4 | 5 | ```ruby 6 | factory :user do 7 | after(:build) { |user| do_something_to(user) } 8 | after(:create) { |user| do_something_else_to(user) } 9 | end 10 | ``` 11 | 12 | Factories can also define any number of the same kind of callback. These 13 | callbacks will be executed in the order they are specified: 14 | 15 | ```ruby 16 | factory :user do 17 | after(:create) { this_runs_first } 18 | after(:create) { then_this } 19 | end 20 | ``` 21 | 22 | Calling `create` will invoke both `after_build` and `after_create` callbacks. 23 | 24 | Also, like standard attributes, child factories will inherit (and can also 25 | define) callbacks from their parent factory. 26 | 27 | Multiple callbacks can be assigned to run a block; this is useful when building 28 | various strategies that run the same code (since there are no callbacks that are 29 | shared across all strategies). 30 | 31 | ```ruby 32 | factory :user do 33 | callback(:after_stub, :before_create) { do_something } 34 | after(:stub, :create) { do_something_else } 35 | before(:create, :custom) { do_a_third_thing } 36 | end 37 | ``` 38 | -------------------------------------------------------------------------------- /lib/factory_bot/errors.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # Raised when a factory is defined that attempts to instantiate itself. 3 | class AssociationDefinitionError < RuntimeError; end 4 | 5 | # Raised when a trait is defined that references itself. 6 | class TraitDefinitionError < RuntimeError; end 7 | 8 | # Raised when a callback is defined that has an invalid name 9 | class InvalidCallbackNameError < RuntimeError; end 10 | 11 | # Raised when a factory is defined with the same name as a previously-defined factory. 12 | class DuplicateDefinitionError < RuntimeError; end 13 | 14 | # Raised when attempting to register a sequence from a dynamic attribute block 15 | class SequenceAbuseError < RuntimeError; end 16 | 17 | # Raised when defining an attribute twice in the same factory 18 | class AttributeDefinitionError < RuntimeError; end 19 | 20 | # Raised when attempting to pass a block to an association definition 21 | class AssociationDefinitionError < RuntimeError; end 22 | 23 | # Raised when a method is defined in a factory or trait with arguments 24 | class MethodDefinitionError < RuntimeError; end 25 | 26 | # Raised when any factory is considered invalid 27 | class InvalidFactoryError < RuntimeError; end 28 | end 29 | -------------------------------------------------------------------------------- /spec/support/matchers/delegate.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :delegate do |delegated_method| 2 | chain :to do |target_method| 3 | @target_method = target_method 4 | end 5 | 6 | chain :as do |method_on_target| 7 | @method_on_target = method_on_target 8 | end 9 | 10 | chain :with_arguments do |args| 11 | @args = args 12 | end 13 | 14 | match do |instance| 15 | @instance = instance 16 | @args ||= [] 17 | return_value = "stubbed return value" 18 | method_on_target = @method_on_target || delegated_method 19 | stubbed_target = double("stubbed_target", method_on_target => return_value) 20 | allow(@instance).to receive(@target_method).and_return stubbed_target 21 | begin 22 | @instance.send(delegated_method, *@args) == return_value 23 | rescue NoMethodError 24 | false 25 | end 26 | end 27 | 28 | failure_message do 29 | if Class === @instance 30 | message = "expected #{@instance.name} " 31 | prefix = "." 32 | else 33 | message = "expected #{@instance.class.name} " 34 | prefix = "#" 35 | end 36 | message << "to delegate #{prefix}#{delegated_method} to #{prefix}#{@target_method}" 37 | if @method_on_target 38 | message << ".#{@method_on_target}" 39 | end 40 | message 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/factory_bot/attribute/association_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Attribute::Association do 2 | let(:name) { :author } 3 | let(:factory) { :user } 4 | let(:overrides) { {first_name: "John"} } 5 | let(:association) { double("association") } 6 | 7 | subject { FactoryBot::Attribute::Association.new(name, factory, overrides) } 8 | 9 | before do 10 | # Define an '#association' instance method allowing it to be mocked. 11 | # Usually this is determined via '#method_missing' 12 | missing_methods = Module.new { 13 | def association(*args) 14 | end 15 | } 16 | subject.extend(missing_methods) 17 | 18 | allow(subject) 19 | .to receive(:association).with(any_args).and_return association 20 | end 21 | 22 | it { should be_association } 23 | its(:name) { should eq name } 24 | 25 | it "builds the association when calling the proc" do 26 | expect(subject.to_proc.call).to eq association 27 | end 28 | 29 | it "builds the association when calling the proc" do 30 | subject.to_proc.call 31 | expect(subject).to have_received(:association).with(factory, overrides) 32 | end 33 | end 34 | 35 | describe FactoryBot::Attribute::Association, "with a string name" do 36 | subject { FactoryBot::Attribute::Association.new("name", :user, {}) } 37 | its(:name) { should eq :name } 38 | end 39 | -------------------------------------------------------------------------------- /docs/src/cookbook/has_and_belongs_to_many-associations.md: -------------------------------------------------------------------------------- 1 | # has_and_belongs_to_many associations 2 | 3 | Generating data for a `has_and_belongs_to_many` relationship is very similar 4 | to the above `has_many` relationship, with a small change: you need to pass an 5 | array of objects to the model's pluralized attribute name rather than a single 6 | object to the singular version of the attribute name. 7 | 8 | 9 | ```ruby 10 | def profile_with_languages(languages_count: 2) 11 | FactoryBot.create(:profile) do |profile| 12 | FactoryBot.create_list(:language, languages_count, profiles: [profile]) 13 | end 14 | end 15 | ``` 16 | 17 | Or with the callback approach: 18 | 19 | ```ruby 20 | factory :profile_with_languages do 21 | transient do 22 | languages_count { 2 } 23 | end 24 | 25 | after(:create) do |profile, context| 26 | create_list(:language, context.languages_count, profiles: [profile]) 27 | profile.reload 28 | end 29 | end 30 | ``` 31 | 32 | Or the inline association approach (note the use of the `instance` method here 33 | to refer to the profile being built): 34 | 35 | ```ruby 36 | factory :profile_with_languages do 37 | transient do 38 | languages_count { 2 } 39 | end 40 | 41 | languages do 42 | Array.new(languages_count) do 43 | association(:language, profiles: [instance]) 44 | end 45 | end 46 | end 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/src/ref/build-and-create.md: -------------------------------------------------------------------------------- 1 | # skip_create, to_create, and initialize_with 2 | 3 | The `skip_create`, `to_create`, and `initialize_with` methods control how 4 | factory\_bot interacts with the [build strategies](build-strategies.html). 5 | 6 | These methods can be called within a `factory` definition block, to scope their 7 | effects to just that factory; or within `FactoryBot.define`, to affect global 8 | change. 9 | 10 | ## initialize_with 11 | 12 | The `initialize_with` method takes a block and returns an instance of the 13 | factory's class. It has access to the `attributes` method, which is a hash of 14 | all the fields and values for the object. 15 | 16 | The default definition is: 17 | 18 | ```ruby 19 | initialize_with { new } 20 | ``` 21 | 22 | ## to_create 23 | 24 | The `to_create` method lets you control the `FactoryBot.create` strategy. This 25 | method takes a block which takes the object as constructed by 26 | `initialize_with`, and the factory\_bot context. The context has additional 27 | data from any [`transient`] blocks. 28 | 29 | [`transient`]: transient.html 30 | 31 | The default definition is: 32 | 33 | ```ruby 34 | to_create { |obj, context| obj.save! } 35 | ``` 36 | 37 | The `skip_create` method is a shorthand for turning `to_create` into a no-op. 38 | This allows you to use the `create` strategy as a synonym for `build`, except 39 | you additionally get any `create` hooks. 40 | -------------------------------------------------------------------------------- /spec/factory_bot/declaration/association_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Declaration::Association do 2 | describe "#==" do 3 | context "when the attributes are equal" do 4 | it "the objects are equal" do 5 | declaration = described_class.new(:name, options: true) 6 | other_declaration = described_class.new(:name, options: true) 7 | 8 | expect(declaration).to eq(other_declaration) 9 | end 10 | end 11 | 12 | context "when the names are different" do 13 | it "the objects are NOT equal" do 14 | declaration = described_class.new(:name, options: true) 15 | other_declaration = described_class.new(:other_name, options: true) 16 | 17 | expect(declaration).not_to eq(other_declaration) 18 | end 19 | end 20 | 21 | context "when the options are different" do 22 | it "the objects are NOT equal" do 23 | declaration = described_class.new(:name, options: true) 24 | other_declaration = described_class.new(:name, other_options: true) 25 | 26 | expect(declaration).not_to eq(other_declaration) 27 | end 28 | end 29 | 30 | context "when comparing against another type of object" do 31 | it "the objects are NOT equal" do 32 | declaration = described_class.new(:name) 33 | 34 | expect(declaration).not_to eq(:not_a_declaration) 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/acceptance/definition_spec.rb: -------------------------------------------------------------------------------- 1 | describe "an instance generated by a factory with a custom class name" do 2 | before do 3 | define_model("User", admin: :boolean) 4 | 5 | FactoryBot.define do 6 | factory :user 7 | 8 | factory :admin, class: User do 9 | admin { true } 10 | end 11 | end 12 | end 13 | 14 | subject { FactoryBot.create(:admin) } 15 | 16 | it { should be_kind_of(User) } 17 | it { should be_admin } 18 | end 19 | 20 | describe "attributes defined using Symbol#to_proc" do 21 | before do 22 | define_model("User", password: :string, password_confirmation: :string) 23 | 24 | FactoryBot.define do 25 | factory :user do 26 | password { "foo" } 27 | password_confirmation(&:password) 28 | end 29 | end 30 | end 31 | 32 | it "assigns values correctly" do 33 | user = FactoryBot.build(:user) 34 | 35 | expect(user.password).to eq "foo" 36 | expect(user.password_confirmation).to eq "foo" 37 | end 38 | 39 | it "assigns value with override correctly" do 40 | user = FactoryBot.build(:user, password: "bar") 41 | 42 | expect(user.password).to eq "bar" 43 | expect(user.password_confirmation).to eq "bar" 44 | end 45 | 46 | it "assigns overridden value correctly" do 47 | user = FactoryBot.build(:user, password_confirmation: "bar") 48 | 49 | expect(user.password).to eq "foo" 50 | expect(user.password_confirmation).to eq "bar" 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /.github/REPRODUCTION_SCRIPT.rb: -------------------------------------------------------------------------------- 1 | require "bundler/inline" 2 | 3 | gemfile(true) do 4 | source "https://rubygems.org" 5 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 6 | gem "factory_bot", "~> 6.0" 7 | gem "activerecord" 8 | gem "sqlite3" 9 | end 10 | 11 | require "active_record" 12 | require "factory_bot" 13 | require "minitest/autorun" 14 | require "logger" 15 | 16 | ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") 17 | ActiveRecord::Base.logger = Logger.new(STDOUT) 18 | 19 | ActiveRecord::Schema.define do 20 | # TODO: Update the schema to include the specific tables or columns necessary 21 | # to reproduct the bug 22 | create_table :posts, force: true do |t| 23 | t.string :body 24 | end 25 | end 26 | 27 | # TODO: Add any application specific code necessary to reproduce the bug 28 | class Post < ActiveRecord::Base 29 | end 30 | 31 | FactoryBot.define do 32 | # TODO: Write the factory definitions necessary to reproduce the bug 33 | factory :post do 34 | body { "Post body" } 35 | end 36 | end 37 | 38 | class FactoryBotTest < Minitest::Test 39 | def test_factory_bot_stuff 40 | # TODO: Write a failing test case to demonstrate what isn't working as 41 | # expected 42 | body_override = "Body override" 43 | 44 | post = FactoryBot.build(:post, body: body_override) 45 | 46 | assert_equal post.body, body_override 47 | end 48 | end 49 | 50 | # Run the tests with `ruby ` 51 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | - push 4 | - pull_request 5 | 6 | jobs: 7 | build: 8 | name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} 9 | if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | ruby: 14 | - jruby-9.4 15 | - truffleruby 16 | - "3.2" 17 | - "3.1" 18 | - "3.0" 19 | rails: 20 | - "6.1" 21 | - "7.0" 22 | - "7.1" 23 | - main 24 | exclude: 25 | - ruby: jruby-9.4 26 | rails: "7.1" 27 | - ruby: jruby-9.4 28 | rails: main 29 | 30 | runs-on: 'ubuntu-latest' 31 | 32 | env: 33 | BUNDLE_GEMFILE: gemfiles/${{ matrix.rails }}.gemfile 34 | 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: ruby/setup-ruby@v1 38 | with: 39 | ruby-version: ${{ matrix.ruby }} 40 | - name: Setup project 41 | run: bundle install 42 | - name: Run test 43 | run: bundle exec rake all_specs 44 | 45 | standard: 46 | name: Run standard 47 | runs-on: 'ubuntu-latest' 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: ruby/setup-ruby@v1 51 | with: 52 | ruby-version: "3.1" 53 | - name: Setup project 54 | run: bundle install 55 | - name: Run test 56 | run: bundle exec rake standard 57 | -------------------------------------------------------------------------------- /lib/factory_bot/sequence.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # Sequences are defined using sequence within a FactoryBot.define block. 3 | # Sequence values are generated using next. 4 | # @api private 5 | class Sequence 6 | attr_reader :name 7 | 8 | def initialize(name, *args, &proc) 9 | @name = name 10 | @proc = proc 11 | 12 | options = args.extract_options! 13 | @value = args.first || 1 14 | @aliases = options.fetch(:aliases) { [] } 15 | 16 | unless @value.respond_to?(:peek) 17 | @value = EnumeratorAdapter.new(@value) 18 | end 19 | end 20 | 21 | def next(scope = nil) 22 | if @proc && scope 23 | scope.instance_exec(value, &@proc) 24 | elsif @proc 25 | @proc.call(value) 26 | else 27 | value 28 | end 29 | ensure 30 | increment_value 31 | end 32 | 33 | def names 34 | [@name] + @aliases 35 | end 36 | 37 | def rewind 38 | @value.rewind 39 | end 40 | 41 | private 42 | 43 | def value 44 | @value.peek 45 | end 46 | 47 | def increment_value 48 | @value.next 49 | end 50 | 51 | class EnumeratorAdapter 52 | def initialize(value) 53 | @first_value = value 54 | @value = value 55 | end 56 | 57 | def peek 58 | @value 59 | end 60 | 61 | def next 62 | @value = @value.next 63 | end 64 | 65 | def rewind 66 | @value = @first_value 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /docs/src/summary.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | 3 | factory_bot is a fixtures replacement with a straightforward definition syntax, 4 | support for multiple build strategies (saved instances, unsaved instances, 5 | attribute hashes, and stubbed objects), and support for multiple factories for 6 | the same class (user, admin_user, and so on), including factory inheritance. 7 | 8 | Its documentation is split as such: 9 | 10 | - the [guide](setup/summary.html) is a great place to start for first-time users. 11 | - the [cookbook](cookbook/has_many-associations.html) is the go-to place for creative solutions to common situations. 12 | - the [wiki](https://github.com/thoughtbot/factory_bot/wiki) details considerations for integrating with other software. 13 | - the [reference](ref/build-strategies.html) is terse facts for those who use this project often. 14 | 15 | ## License 16 | 17 | factory_bot is Copyright © 2008 Joe Ferris and thoughtbot. It is free 18 | software, and may be redistributed under the terms specified in the 19 | [LICENSE] file. 20 | 21 | [LICENSE]: https://github.com/thoughtbot/factory_bot/blob/main/LICENSE 22 | 23 | 24 | ## About thoughtbot 25 | 26 | factory_bot is maintained and funded by thoughtbot, inc. 27 | The names and logos for thoughtbot are trademarks of thoughtbot, inc. 28 | 29 | We love open source software! 30 | See [our other projects][community] or 31 | [hire us][hire] to design, develop, and grow your product. 32 | 33 | [community]: https://thoughtbot.com/community?utm_source=github 34 | [hire]: https://thoughtbot.com/hire-us?utm_source=github 35 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Mike Burns"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "factory_bot" 7 | 8 | [output.html.redirect] 9 | "/setup/rspec.html" = "https://github.com/thoughtbot/factory_bot/wiki/Integration:-RSpec" 10 | "/setup/cucumber.html" = "https://github.com/thoughtbot/factory_bot/wiki/Integration:-Cucumber" 11 | "/setup/minitest-rails.html" = "https://github.com/thoughtbot/factory_bot/wiki/Integration:-minitest-rails" 12 | "/setup/minitest-spec.html" = "https://github.com/thoughtbot/factory_bot/wiki/Integration:-Minitest::Spec" 13 | "/setup/minitest.html" = "https://github.com/thoughtbot/factory_bot/wiki/Integration:-Minitest" 14 | "/setup/spinach.html" = "https://github.com/thoughtbot/factory_bot/wiki/Integration:-Spinach" 15 | "/setup/test-unit.html" = "https://github.com/thoughtbot/factory_bot/wiki/Integration:-Test::Unit" 16 | "/setup/update-gemfile.html" = "summary.html" 17 | "/setup/configure-test-suite.html" = "summary.html" 18 | "/using-factories/build_stubbed-and-marshaldump.html" = "build-strategies.html" 19 | "/associations/has_many-associations.html" = "../cookbook/has_many-associations.html" 20 | "/associations/has_and_belongs_to_many-associations.html" = "../cookbook/has_and_belongs_to_many-associations.html" 21 | "/associations/polymorphic-associations.html" = "../cookbook/polymorphic-associations.html" 22 | "/associations/interconnected-associations.html" = "../cookbook/interconnected-associations.html" 23 | "/traits/defining-traits.html" = "summary.html" 24 | "/callbacks/default-callbacks.html" = "summary.html" 25 | -------------------------------------------------------------------------------- /spec/acceptance/overrides_spec.rb: -------------------------------------------------------------------------------- 1 | describe "attribute overrides" do 2 | before do 3 | define_model("User", admin: :boolean) 4 | define_model("Post", title: :string, 5 | secure: :boolean, 6 | user_id: :integer) do 7 | belongs_to :user 8 | 9 | def secure=(value) 10 | return unless user&.admin? 11 | 12 | write_attribute(:secure, value) 13 | end 14 | end 15 | 16 | FactoryBot.define do 17 | factory :user do 18 | factory :admin do 19 | admin { true } 20 | end 21 | end 22 | 23 | factory :post do 24 | user 25 | title { "default title" } 26 | end 27 | end 28 | end 29 | 30 | let(:admin) { FactoryBot.create(:admin) } 31 | 32 | let(:post_attributes) do 33 | {secure: false} 34 | end 35 | 36 | let(:non_admin_post_attributes) do 37 | post_attributes[:user] = FactoryBot.create(:user) 38 | post_attributes 39 | end 40 | 41 | let(:admin_post_attributes) do 42 | post_attributes[:user] = admin 43 | post_attributes 44 | end 45 | 46 | context "with an admin posting" do 47 | subject { FactoryBot.create(:post, admin_post_attributes) } 48 | its(:secure) { should eq false } 49 | end 50 | 51 | context "with a non-admin posting" do 52 | subject { FactoryBot.create(:post, non_admin_post_attributes) } 53 | its(:secure) { should be_nil } 54 | end 55 | 56 | context "with no user posting" do 57 | subject { FactoryBot.create(:post, post_attributes) } 58 | its(:secure) { should be_nil } 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/acceptance/sequence_context_spec.rb: -------------------------------------------------------------------------------- 1 | describe "sequences are evaluated in the correct context" do 2 | before do 3 | define_class("User") do 4 | attr_accessor :id 5 | 6 | def awesome 7 | "aw yeah" 8 | end 9 | end 10 | end 11 | 12 | it "builds a sequence calling sprintf correctly" do 13 | FactoryBot.define do 14 | factory :sequence_with_sprintf, class: User do 15 | sequence(:id) { |n| sprintf("foo%d", n) } 16 | end 17 | end 18 | 19 | expect(FactoryBot.build(:sequence_with_sprintf).id).to eq "foo1" 20 | end 21 | 22 | it "invokes the correct method on the instance" do 23 | FactoryBot.define do 24 | factory :sequence_with_public_method, class: User do 25 | sequence(:id) { public_method(:awesome).call } 26 | end 27 | end 28 | 29 | expect(FactoryBot.build(:sequence_with_public_method).id).to eq "aw yeah" 30 | end 31 | 32 | it "invokes a method with no arguments on the instance" do 33 | FactoryBot.define do 34 | factory :sequence_with_frozen, class: User do 35 | sequence(:id) { frozen? } 36 | end 37 | end 38 | 39 | expect(FactoryBot.build(:sequence_with_frozen).id).to be false 40 | end 41 | 42 | it "allows direct reference of a method in a sequence" do 43 | FactoryBot.define do 44 | factory :sequence_referencing_attribute_directly, class: User do 45 | sequence(:id) { |n| "#{awesome}#{n}" } 46 | end 47 | end 48 | expect(FactoryBot.build(:sequence_referencing_attribute_directly).id).to eq "aw yeah1" 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/factory_bot/registry.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/hash/indifferent_access" 2 | 3 | module FactoryBot 4 | class Registry 5 | include Enumerable 6 | 7 | attr_reader :name 8 | 9 | def initialize(name) 10 | @name = name 11 | @items = ActiveSupport::HashWithIndifferentAccess.new 12 | end 13 | 14 | def clear 15 | @items.clear 16 | end 17 | 18 | def each(&block) 19 | @items.values.uniq.each(&block) 20 | end 21 | 22 | def find(name) 23 | @items.fetch(name) 24 | rescue KeyError => e 25 | raise key_error_with_custom_message(e) 26 | end 27 | 28 | alias_method :[], :find 29 | 30 | def register(name, item) 31 | @items[name] = item 32 | end 33 | 34 | def registered?(name) 35 | @items.key?(name) 36 | end 37 | 38 | private 39 | 40 | def key_error_with_custom_message(key_error) 41 | message = key_error.message.sub("key not found", "#{@name} not registered") 42 | new_key_error(message, key_error).tap do |error| 43 | error.set_backtrace(key_error.backtrace) 44 | end 45 | end 46 | 47 | # detailed_message introduced in Ruby 3.2 for cleaner integration with 48 | # did_you_mean. See https://bugs.ruby-lang.org/issues/18564 49 | if KeyError.method_defined?(:detailed_message) 50 | def new_key_error(message, key_error) 51 | KeyError.new(message, key: key_error.key, receiver: key_error.receiver) 52 | end 53 | else 54 | def new_key_error(message, _) 55 | KeyError.new(message) 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/acceptance/modify_inherited_spec.rb: -------------------------------------------------------------------------------- 1 | describe "modifying inherited factories with traits" do 2 | before do 3 | define_model("User", gender: :string, admin: :boolean, age: :integer) 4 | FactoryBot.define do 5 | factory :user do 6 | trait(:female) { gender { "Female" } } 7 | trait(:male) { gender { "Male" } } 8 | 9 | trait(:young_admin) do 10 | admin { true } 11 | age { 17 } 12 | end 13 | 14 | female 15 | young_admin 16 | 17 | factory :female_user do 18 | gender { "Female" } 19 | age { 25 } 20 | end 21 | 22 | factory :male_user do 23 | gender { "Male" } 24 | end 25 | end 26 | end 27 | end 28 | 29 | it "returns the correct value for overridden attributes from traits" do 30 | expect(FactoryBot.build(:male_user).gender).to eq "Male" 31 | end 32 | 33 | it "returns the correct value for overridden attributes from traits defining multiple attributes" do 34 | expect(FactoryBot.build(:female_user).gender).to eq "Female" 35 | expect(FactoryBot.build(:female_user).age).to eq 25 36 | expect(FactoryBot.build(:female_user).admin).to eq true 37 | end 38 | 39 | it "allows modification of attributes created via traits" do 40 | FactoryBot.modify do 41 | factory :male_user do 42 | age { 20 } 43 | end 44 | end 45 | 46 | expect(FactoryBot.build(:male_user).gender).to eq "Male" 47 | expect(FactoryBot.build(:male_user).age).to eq 20 48 | expect(FactoryBot.build(:male_user).admin).to eq true 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /docs/src/ref/hooks.md: -------------------------------------------------------------------------------- 1 | # Hooks 2 | 3 | Within a `factory` definition block and the `FactoryBot.define` block, you have 4 | access to the `after`, `before`, and `callback` methods. This allow you to hook 5 | into parts of the [build strategies]. 6 | 7 | [build strategies]: build-strategies.html 8 | 9 | Within a `factory` definition block, these callbacks are scoped to just that 10 | factory. Within a `FactoryBot.define` block, they are global to all factories. 11 | 12 | ## `callback` 13 | 14 | The `callback` method allows you to hook into any factory\_bot callback by 15 | name. The pre-defined names, as seen in the [build strategies] reference, are 16 | `after_build`, `before_create`, `after_create`, and `after_stub`. 17 | 18 | This method takes a splat of names, and a block. It invokes the block any time 19 | one of the names is activated. The block can be anything that responds to 20 | `#to_proc`. 21 | 22 | This block takes two arguments: the instance of the factory, and the 23 | factory\_bot context. The context holds [transient](transient.html) 24 | attributes. 25 | 26 | The same callback name can be hooked into multiple times. Every block is run, 27 | in the order it was defined. Callbacks are inherited from their parents; the 28 | parents' callbacks are run first. 29 | 30 | ## `after` and `before` methods 31 | 32 | The `after` and `before` methods add some nice syntax to `callback`: 33 | 34 | ```ruby 35 | after(:create) do |user, context| 36 | user.post_first_article(context.article) 37 | end 38 | 39 | callback(:after_create) do |user, context| 40 | user.post_first_article(context.article) 41 | end 42 | ``` 43 | -------------------------------------------------------------------------------- /spec/acceptance/attributes_ordered_spec.rb: -------------------------------------------------------------------------------- 1 | describe "a generated attributes hash where order matters" do 2 | include FactoryBot::Syntax::Methods 3 | 4 | before do 5 | define_model("ParentModel", static: :integer, 6 | evaluates_first: :integer, 7 | evaluates_second: :integer, 8 | evaluates_third: :integer) 9 | 10 | FactoryBot.define do 11 | factory :parent_model do 12 | evaluates_first { static } 13 | evaluates_second { evaluates_first } 14 | evaluates_third { evaluates_second } 15 | 16 | factory :child_model do 17 | static { 1 } 18 | end 19 | end 20 | 21 | factory :without_parent, class: ParentModel do 22 | evaluates_first { static } 23 | evaluates_second { evaluates_first } 24 | evaluates_third { evaluates_second } 25 | static { 1 } 26 | end 27 | end 28 | end 29 | 30 | context "factory with a parent" do 31 | subject { FactoryBot.build(:child_model) } 32 | 33 | it "assigns attributes in the order they're defined" do 34 | expect(subject[:evaluates_first]).to eq 1 35 | expect(subject[:evaluates_second]).to eq 1 36 | expect(subject[:evaluates_third]).to eq 1 37 | end 38 | end 39 | 40 | context "factory without a parent" do 41 | subject { FactoryBot.build(:without_parent) } 42 | 43 | it "assigns attributes in the order they're defined without a parent class" do 44 | expect(subject[:evaluates_first]).to eq 1 45 | expect(subject[:evaluates_second]).to eq 1 46 | expect(subject[:evaluates_third]).to eq 1 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /docs/src/modifying-factories/summary.md: -------------------------------------------------------------------------------- 1 | # Modifying factories 2 | 3 | If you're given a set of factories (say, from a gem developer) but want to 4 | change them to fit into your application better, you can modify that factory 5 | instead of creating a child factory and adding attributes there. 6 | 7 | If a gem were to give you a User factory: 8 | 9 | ```ruby 10 | FactoryBot.define do 11 | factory :user do 12 | full_name { "John Doe" } 13 | sequence(:username) { |n| "user#{n}" } 14 | password { "password" } 15 | end 16 | end 17 | ``` 18 | 19 | Instead of creating a child factory that added additional attributes: 20 | 21 | ```ruby 22 | FactoryBot.define do 23 | factory :application_user, parent: :user do 24 | full_name { "Jane Doe" } 25 | date_of_birth { 21.years.ago } 26 | health { 90 } 27 | end 28 | end 29 | ``` 30 | 31 | You could modify that factory instead. 32 | 33 | ```ruby 34 | FactoryBot.modify do 35 | factory :user do 36 | full_name { "Jane Doe" } 37 | date_of_birth { 21.years.ago } 38 | health { 90 } 39 | end 40 | end 41 | ``` 42 | 43 | When modifying a factory, you can change any of the attributes you want (aside from callbacks). 44 | 45 | `FactoryBot.modify` must be called outside of a `FactoryBot.define` block as it 46 | operates on factories differently. 47 | 48 | A caveat: you can only modify factories (not sequences or traits), and 49 | callbacks *still compound as they normally would*. So, if the factory you're 50 | modifying defines an `after(:create)` callback, you defining an 51 | `after(:create)` won't override it, it will instead be run after the first 52 | callback. 53 | -------------------------------------------------------------------------------- /spec/acceptance/syntax_methods_within_dynamic_attributes_spec.rb: -------------------------------------------------------------------------------- 1 | describe "syntax methods within dynamic attributes" do 2 | before do 3 | define_model("Post", title: :string, user_id: :integer) do 4 | belongs_to :user 5 | 6 | def generate 7 | "generate result" 8 | end 9 | end 10 | define_model("User", email: :string) 11 | 12 | FactoryBot.define do 13 | sequence(:email_address) { |n| "person-#{n}@example.com" } 14 | 15 | factory :user do 16 | email { generate(:email_address) } 17 | end 18 | 19 | factory :post do 20 | title { generate } 21 | user { build(:user) } 22 | end 23 | end 24 | end 25 | 26 | it "can access syntax methods from dynamic attributes" do 27 | expect(FactoryBot.build(:user).email).to eq "person-1@example.com" 28 | expect(FactoryBot.attributes_for(:user)[:email]).to eq "person-2@example.com" 29 | end 30 | 31 | it "can access syntax methods from dynamic attributes" do 32 | expect(FactoryBot.build(:post).user).to be_instance_of(User) 33 | end 34 | 35 | it "can access methods already existing on the class" do 36 | expect(FactoryBot.build(:post).title).to eq "generate result" 37 | expect(FactoryBot.attributes_for(:post)[:title]).to be_nil 38 | end 39 | 40 | it "allows syntax methods to be used when the block has an arity of 1" do 41 | FactoryBot.define do 42 | factory :post_using_block_with_variable, parent: :post do 43 | user { |_| build(:user) } 44 | end 45 | end 46 | 47 | expect(FactoryBot.build(:post_using_block_with_variable).user).to be_instance_of(User) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/factory_bot/null_factory_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::NullFactory do 2 | it "delegates defined traits to its definition" do 3 | null_factory = FactoryBot::NullFactory.new 4 | 5 | expect(null_factory).to delegate(:defined_traits).to(:definition) 6 | end 7 | 8 | it "delegates callbacks to its definition" do 9 | null_factory = FactoryBot::NullFactory.new 10 | 11 | expect(null_factory).to delegate(:callbacks).to(:definition) 12 | end 13 | 14 | it "delegates attributes to its definition" do 15 | null_factory = FactoryBot::NullFactory.new 16 | 17 | expect(null_factory).to delegate(:attributes).to(:definition) 18 | end 19 | 20 | it "delegates constructor to its definition" do 21 | null_factory = FactoryBot::NullFactory.new 22 | 23 | expect(null_factory).to delegate(:constructor).to(:definition) 24 | end 25 | 26 | it "has a nil value for its compile attribute" do 27 | null_factory = FactoryBot::NullFactory.new 28 | 29 | expect(null_factory.compile).to be_nil 30 | end 31 | 32 | it "has a nil value for its class_name attribute" do 33 | null_factory = FactoryBot::NullFactory.new 34 | 35 | expect(null_factory.class_name).to be_nil 36 | end 37 | 38 | it "has an instance of FactoryBot::AttributeList for its attributes attribute" do 39 | null_factory = FactoryBot::NullFactory.new 40 | 41 | expect(null_factory.attributes).to be_an_instance_of(FactoryBot::AttributeList) 42 | end 43 | 44 | it "has FactoryBot::Evaluator as its evaluator class" do 45 | null_factory = FactoryBot::NullFactory.new 46 | 47 | expect(null_factory.evaluator_class).to eq FactoryBot::Evaluator 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /factory_bot.gemspec: -------------------------------------------------------------------------------- 1 | $LOAD_PATH << File.expand_path("lib", __dir__) 2 | require "factory_bot/version" 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "factory_bot" 6 | s.version = FactoryBot::VERSION 7 | s.summary = "factory_bot provides a framework and DSL for defining and " \ 8 | "using model instance factories." 9 | s.description = "factory_bot provides a framework and DSL for defining and " \ 10 | "using factories - less error-prone, more explicit, and " \ 11 | "all-around easier to work with than fixtures." 12 | 13 | s.files = 14 | Dir.glob("lib/**/*") + 15 | %w[CONTRIBUTING.md GETTING_STARTED.md LICENSE NAME.md NEWS.md README.md .yardopts] 16 | 17 | s.require_path = "lib" 18 | s.required_ruby_version = Gem::Requirement.new(">= 2.5.0") 19 | 20 | s.authors = ["Josh Clayton", "Joe Ferris"] 21 | s.email = ["jclayton@thoughtbot.com", "jferris@thoughtbot.com"] 22 | 23 | s.homepage = "https://github.com/thoughtbot/factory_bot" 24 | 25 | s.metadata = { 26 | "changelog_uri" => "https://github.com/thoughtbot/factory_bot/blob/main/NEWS.md" 27 | } 28 | 29 | s.add_dependency("activesupport", ">= 5.0.0") 30 | 31 | s.add_development_dependency("activerecord") 32 | s.add_development_dependency("appraisal") 33 | s.add_development_dependency("aruba") 34 | s.add_development_dependency("cucumber") 35 | s.add_development_dependency("rake") 36 | s.add_development_dependency("rspec") 37 | s.add_development_dependency("rspec-its") 38 | s.add_development_dependency("standard") 39 | s.add_development_dependency("simplecov") 40 | s.add_development_dependency("yard") 41 | 42 | s.license = "MIT" 43 | end 44 | -------------------------------------------------------------------------------- /spec/acceptance/attributes_from_instance_spec.rb: -------------------------------------------------------------------------------- 1 | describe "calling methods on the model instance" do 2 | before do 3 | define_model("User", age: :integer, age_copy: :integer) do 4 | def age 5 | read_attribute(:age) || 18 6 | end 7 | end 8 | 9 | FactoryBot.define do 10 | factory :user do 11 | age_copy { age } 12 | end 13 | end 14 | end 15 | 16 | context "without the attribute being overridden" do 17 | it "returns the correct value from the instance" do 18 | expect(FactoryBot.build(:user).age_copy).to eq 18 19 | end 20 | 21 | it "returns nil during attributes_for" do 22 | expect(FactoryBot.attributes_for(:user)[:age_copy]).to be_nil 23 | end 24 | 25 | it "doesn't instantiate a record with attributes_for" do 26 | allow(User).to receive(:new) 27 | FactoryBot.attributes_for(:user) 28 | expect(User).to_not have_received(:new) 29 | end 30 | end 31 | 32 | context "with the attribute being overridden" do 33 | it "uses the overridden value" do 34 | expect(FactoryBot.build(:user, age_copy: nil).age_copy).to be_nil 35 | end 36 | 37 | it "uses the overridden value during attributes_for" do 38 | expect(FactoryBot.attributes_for(:user, age_copy: 25)[:age_copy]).to eq 25 39 | end 40 | end 41 | 42 | context "with the referenced attribute being overridden" do 43 | it "uses the overridden value" do 44 | expect(FactoryBot.build(:user, age: nil).age_copy).to be_nil 45 | end 46 | 47 | it "uses the overridden value during attributes_for" do 48 | expect(FactoryBot.attributes_for(:user, age: 25)[:age_copy]).to eq 25 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/factory_bot/declaration/association.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | class Declaration 3 | # @api private 4 | class Association < Declaration 5 | def initialize(name, *options) 6 | super(name, false) 7 | @options = options.dup 8 | @overrides = options.extract_options! 9 | @factory_name = @overrides.delete(:factory) || name 10 | @traits = options 11 | end 12 | 13 | def ==(other) 14 | self.class == other.class && 15 | name == other.name && 16 | options == other.options 17 | end 18 | 19 | protected 20 | 21 | attr_reader :options 22 | 23 | private 24 | 25 | attr_reader :factory_name, :overrides, :traits 26 | 27 | def build 28 | raise_if_arguments_are_declarations! 29 | 30 | [ 31 | Attribute::Association.new( 32 | name, 33 | factory_name, 34 | [traits, overrides].flatten 35 | ) 36 | ] 37 | end 38 | 39 | def raise_if_arguments_are_declarations! 40 | if factory_name.is_a?(Declaration) 41 | raise ArgumentError.new(<<~MSG) 42 | Association '#{name}' received an invalid factory argument. 43 | Did you mean? 'factory: :#{factory_name.name}' 44 | MSG 45 | end 46 | 47 | overrides.each do |attribute, value| 48 | if value.is_a?(Declaration) 49 | raise ArgumentError.new(<<~MSG) 50 | Association '#{name}' received an invalid attribute override. 51 | Did you mean? '#{attribute}: :#{value.name}' 52 | MSG 53 | end 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /docs/src/custom-strategies/summary.md: -------------------------------------------------------------------------------- 1 | # Custom Strategies 2 | 3 | There are times where you may want to extend behavior of factory\_bot by adding 4 | a custom build strategy. 5 | 6 | Strategies define two methods: `association` and `result`. `association` 7 | receives a `FactoryBot::FactoryRunner` instance, upon which you can call `run`, 8 | overriding the strategy if you want. The second method, `result`, receives a 9 | `FactoryBot::Evaluation` instance. It provides a way to trigger callbacks (with 10 | `notify`), `object` or `hash` (to get the result instance or a hash based on 11 | the attributes defined in the factory), and `create`, which executes the 12 | `to_create` callback defined on the factory. 13 | 14 | To understand how factory\_bot uses strategies internally, it's probably 15 | easiest to view the source for each of the four default strategies. 16 | 17 | Here's an example of composing a strategy using `FactoryBot::Strategy::Create` 18 | to build a JSON representation of your model. 19 | 20 | ```ruby 21 | class JsonStrategy 22 | def initialize 23 | @strategy = FactoryBot.strategy_by_name(:create).new 24 | end 25 | 26 | delegate :association, to: :@strategy 27 | 28 | def result(evaluation) 29 | @strategy.result(evaluation).to_json 30 | end 31 | 32 | def to_sym 33 | :json 34 | end 35 | end 36 | ``` 37 | 38 | For factory\_bot to recognize the new strategy, you can register it: 39 | 40 | ```ruby 41 | FactoryBot.register_strategy(:json, JsonStrategy) 42 | ``` 43 | 44 | This allows you to call 45 | 46 | ```ruby 47 | FactoryBot.json(:user) 48 | ``` 49 | 50 | Finally, you can override factory\_bot's own strategies if you'd like by 51 | registering a new object in place of the strategies. 52 | -------------------------------------------------------------------------------- /docs/src/ref/sequence.md: -------------------------------------------------------------------------------- 1 | # sequence 2 | 3 | A factory\_bot set up supports two levels of sequences: global and factory-specific. 4 | 5 | ## Global sequences 6 | 7 | With a [`Factory.define`] block, use the `sequence` method to define global 8 | sequences that can be shared with other factories. 9 | 10 | [`Factory.define`]: define.html 11 | 12 | The `sequence` method takes a name, optional arguments, and a block. The name 13 | is expected to be a Symbol. 14 | 15 | The supported arguments are a number representing the starting value (default: 16 | `1`), and `:aliases` (default `[]`). The starting value must respond to `#next`. 17 | 18 | The block takes a value as an argument, and returns a result. 19 | 20 | The sequence value is incremented globally. Using an `:email_address` sequence 21 | from multiple places increments the value each time. 22 | 23 | See [method_missing](method_missing.html) for a shorthand. 24 | 25 | ## Factory sequences 26 | 27 | Sequences can be localized within `factory` blocks. The syntax is the same as 28 | for a global sequence, but the scope of the incremented value is limited to the 29 | factory definition. 30 | 31 | In addition, using `sequence` with a `factory` block implicitly calls 32 | `add_attribute` for that value. 33 | 34 | These two are similar, except the second example does not cause any global 35 | sequences to exist: 36 | 37 | ```ruby 38 | # A global sequence 39 | sequence(:user_factory_email) { |n| "person#{n}@example.com" } 40 | 41 | factory :user do 42 | # Using a global sequence 43 | email { generate(:user_factory_email) } 44 | end 45 | ``` 46 | 47 | ```ruby 48 | # A factory-scoped sequence 49 | factory :user do 50 | sequence(:email) { |n| "person#{n}@example.com" } 51 | end 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/src/ref/factory.md: -------------------------------------------------------------------------------- 1 | # factory 2 | 3 | Within a `FactoryBot.define` block, you can define factories. Anything defined 4 | using `factory` can be built using a [build strategy](build-strategies.html). 5 | 6 | The `factory` method takes three arguments: a required name, an optional hash 7 | of options, and an optional block. 8 | 9 | The name is expected to be a Symbol. 10 | 11 | ## Options 12 | 13 | - `:class` - what class to construct. This can be either a class, or a String 14 | or Symbol (anything that responds to `#to_s`). By default it is either the 15 | parent's class name or the factory's name. 16 | - `:parent`- the name of another factory that this factory should inherit from. 17 | Defaults to `nil`. 18 | - `:aliases` - alternative names for this factory. Any of these names can be 19 | used with a build strategy. Defaults to the empty list. 20 | - `:traits` - base traits that are used by default when building this factory. 21 | Defaults to the empty list. 22 | 23 | ## Block 24 | 25 | You can use the block to define your factory. Within here you have access to the following methods: 26 | 27 | - [`add_attribute`](ref/add_attribute.md) 28 | - [`association`](ref/association.md) 29 | - [`sequence`](ref/sequence.md) 30 | - [`trait`](ref/trait.md) 31 | - [`method_missing`](ref/method_missing.html) 32 | - [`transient`](ref/transient.md) 33 | - [`traits_for_enum`](ref/traits_for_enum.md) 34 | - [`initialize_with`](ref/build-and-create.md) 35 | - [`skip_create`](ref/build-and-create.md) 36 | - [`to_create`](ref/build-and-create.md) 37 | - [`before`](ref/hooks.md) 38 | - [`after`](ref/hooks.md) 39 | - [`callback`](ref/hooks.md) 40 | - `factory` 41 | 42 | You can use `factory` inside a `factory` block to define a new factory with an 43 | implied parent. 44 | -------------------------------------------------------------------------------- /docs/src/building-or-creating-multiple-records/summary.md: -------------------------------------------------------------------------------- 1 | # Building or Creating Multiple Records 2 | 3 | Sometimes, you'll want to create or build multiple instances of a factory at 4 | once. 5 | 6 | ```ruby 7 | built_users = build_list(:user, 25) 8 | created_users = create_list(:user, 25) 9 | ``` 10 | 11 | These methods will build or create a specific amount of factories and return 12 | them as an array. To set the attributes for each of the factories, you can pass 13 | in a hash as you normally would. 14 | 15 | ```ruby 16 | twenty_year_olds = build_list(:user, 25, date_of_birth: 20.years.ago) 17 | ``` 18 | 19 | In order to set different attributes for each factory, these methods may be 20 | passed a block, with the factory and the index as parameters: 21 | 22 | ```ruby 23 | twenty_somethings = build_list(:user, 10) do |user, i| 24 | user.date_of_birth = (20 + i).years.ago 25 | end 26 | ``` 27 | 28 | `create_list` passes saved instances into the block. If you modify the 29 | instance, you must save it again: 30 | 31 | ```ruby 32 | twenty_somethings = create_list(:user, 10) do |user, i| 33 | user.date_of_birth = (20 + i).years.ago 34 | user.save! 35 | end 36 | ``` 37 | 38 | `build_stubbed_list` will give you fully stubbed out instances: 39 | 40 | ```ruby 41 | stubbed_users = build_stubbed_list(:user, 25) # array of stubbed users 42 | ``` 43 | 44 | There's also a set of `*_pair` methods for creating two records at a time: 45 | 46 | ```ruby 47 | built_users = build_pair(:user) # array of two built users 48 | created_users = create_pair(:user) # array of two created users 49 | ``` 50 | 51 | If you need multiple attribute hashes, `attributes_for_list` will generate 52 | them: 53 | 54 | ```ruby 55 | users_attrs = attributes_for_list(:user, 25) # array of attribute hashes 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /spec/factory_bot/declaration/dynamic_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Declaration::Dynamic do 2 | describe "#==" do 3 | context "when the attributes are equal" do 4 | it "the objects are equal" do 5 | block = -> {} 6 | declaration = described_class.new(:name, false, block) 7 | other_declaration = described_class.new(:name, false, block) 8 | 9 | expect(declaration).to eq(other_declaration) 10 | end 11 | end 12 | 13 | context "when the names are different" do 14 | it "the objects are NOT equal" do 15 | block = -> {} 16 | declaration = described_class.new(:name, false, block) 17 | other_declaration = described_class.new(:other_name, false, block) 18 | 19 | expect(declaration).not_to eq(other_declaration) 20 | end 21 | end 22 | 23 | context "when the blocks are different" do 24 | it "the objects are NOT equal" do 25 | declaration = described_class.new(:name, false, -> {}) 26 | other_declaration = described_class.new(:name, false, -> {}) 27 | 28 | expect(declaration).not_to eq(other_declaration) 29 | end 30 | end 31 | 32 | context "when one is ignored and the other isn't" do 33 | it "the objects are NOT equal" do 34 | block = -> {} 35 | declaration = described_class.new(:name, false, block) 36 | other_declaration = described_class.new(:name, true, block) 37 | 38 | expect(declaration).not_to eq(other_declaration) 39 | end 40 | end 41 | 42 | context "when comparing against another type of object" do 43 | it "the objects are NOT equal" do 44 | declaration = described_class.new(:name, false, -> {}) 45 | 46 | expect(declaration).not_to eq(:not_a_declaration) 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/support/macros/define_constant.rb: -------------------------------------------------------------------------------- 1 | require "active_record" 2 | 3 | module DefineConstantMacros 4 | def define_class(path, base = Object, &block) 5 | const = stub_const(path, Class.new(base)) 6 | const.class_eval(&block) if block 7 | const 8 | end 9 | 10 | def define_model(name, columns = {}, &block) 11 | model = define_class(name, ActiveRecord::Base, &block) 12 | create_table(model.table_name) do |table| 13 | columns.each do |column_name, type| 14 | table.column column_name, type 15 | end 16 | end 17 | model 18 | end 19 | 20 | def create_table(table_name, &block) 21 | connection = ActiveRecord::Base.connection 22 | 23 | begin 24 | connection.execute("DROP TABLE IF EXISTS #{table_name}") 25 | connection.create_table(table_name, &block) 26 | created_tables << table_name 27 | connection 28 | rescue Exception => e # rubocop:disable Lint/RescueException 29 | connection.execute("DROP TABLE IF EXISTS #{table_name}") 30 | raise e 31 | end 32 | end 33 | 34 | def clear_generated_tables 35 | created_tables.each do |table_name| 36 | clear_generated_table(table_name) 37 | end 38 | created_tables.clear 39 | end 40 | 41 | def clear_generated_table(table_name) 42 | ActiveRecord::Base 43 | .connection 44 | .execute("DROP TABLE IF EXISTS #{table_name}") 45 | end 46 | 47 | private 48 | 49 | def created_tables 50 | @created_tables ||= [] 51 | end 52 | end 53 | 54 | RSpec.configure do |config| 55 | config.include DefineConstantMacros 56 | 57 | config.before(:all) do 58 | ActiveRecord::Base.establish_connection( 59 | adapter: "sqlite3", 60 | database: ":memory:" 61 | ) 62 | end 63 | 64 | config.after do 65 | clear_generated_tables 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /docs/src/cookbook/interconnected-associations.md: -------------------------------------------------------------------------------- 1 | # Interconnected associations 2 | 3 | There are limitless ways objects might be interconnected, and 4 | factory\_bot may not always be suited to handle those relationships. In some 5 | cases it makes sense to use factory\_bot to build each individual object, and 6 | then to write helper methods in plain Ruby to tie those objects together. 7 | 8 | That said, some more complex, interconnected relationships can be built in factory\_bot 9 | using inline associations with reference to the `instance` being built. 10 | 11 | Let's say your models look like this, where an associated `Student` and 12 | `Profile` should both belong to the same `School`: 13 | 14 | ```ruby 15 | class Student < ApplicationRecord 16 | belongs_to :school 17 | has_one :profile 18 | end 19 | 20 | class Profile < ApplicationRecord 21 | belongs_to :school 22 | belongs_to :student 23 | end 24 | 25 | class School < ApplicationRecord 26 | has_many :students 27 | has_many :profiles 28 | end 29 | ``` 30 | 31 | We can ensure the student and profile are connected to each other and to the 32 | same school with a factory like this: 33 | 34 | ```ruby 35 | FactoryBot.define do 36 | factory :student do 37 | school 38 | profile { association :profile, student: instance, school: school } 39 | end 40 | 41 | factory :profile do 42 | school 43 | student { association :student, profile: instance, school: school } 44 | end 45 | 46 | factory :school 47 | end 48 | ``` 49 | 50 | Note that this approach works with `build`, `build_stubbed`, and `create`, but 51 | the associations will return `nil` when using `attributes_for`. 52 | 53 | Also, note that if you assign any attributes inside a custom `initialize_with` 54 | (e.g. `initialize_with { new(**attributes) }`), those attributes should not refer to `instance`, 55 | since it will be `nil`. 56 | -------------------------------------------------------------------------------- /lib/factory_bot/attribute_list.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | class AttributeList 4 | include Enumerable 5 | 6 | def initialize(name = nil, attributes = []) 7 | @name = name 8 | @attributes = attributes 9 | end 10 | 11 | def define_attribute(attribute) 12 | ensure_attribute_not_self_referencing! attribute 13 | ensure_attribute_not_defined! attribute 14 | 15 | add_attribute attribute 16 | end 17 | 18 | def each(&block) 19 | @attributes.each(&block) 20 | end 21 | 22 | def names 23 | map(&:name) 24 | end 25 | 26 | def associations 27 | AttributeList.new(@name, select(&:association?)) 28 | end 29 | 30 | def ignored 31 | AttributeList.new(@name, select(&:ignored)) 32 | end 33 | 34 | def non_ignored 35 | AttributeList.new(@name, reject(&:ignored)) 36 | end 37 | 38 | def apply_attributes(attributes_to_apply) 39 | attributes_to_apply.each { |attribute| add_attribute(attribute) } 40 | end 41 | 42 | private 43 | 44 | def add_attribute(attribute) 45 | @attributes << attribute 46 | attribute 47 | end 48 | 49 | def ensure_attribute_not_defined!(attribute) 50 | if attribute_defined?(attribute.name) 51 | raise AttributeDefinitionError, "Attribute already defined: #{attribute.name}" 52 | end 53 | end 54 | 55 | def ensure_attribute_not_self_referencing!(attribute) 56 | if attribute.respond_to?(:factory) && attribute.factory == @name 57 | message = "Self-referencing association '#{attribute.name}' in '#{attribute.factory}'" 58 | raise AssociationDefinitionError, message 59 | end 60 | end 61 | 62 | def attribute_defined?(attribute_name) 63 | @attributes.any? do |attribute| 64 | attribute.name == attribute_name 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/factory_bot/syntax/default.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | module Syntax 3 | module Default 4 | include Methods 5 | 6 | def define(&block) 7 | DSL.run(block) 8 | end 9 | 10 | def modify(&block) 11 | ModifyDSL.run(block) 12 | end 13 | 14 | class DSL 15 | def factory(name, options = {}, &block) 16 | factory = Factory.new(name, options) 17 | proxy = FactoryBot::DefinitionProxy.new(factory.definition) 18 | proxy.instance_eval(&block) if block 19 | 20 | Internal.register_factory(factory) 21 | 22 | proxy.child_factories.each do |(child_name, child_options, child_block)| 23 | parent_factory = child_options.delete(:parent) || name 24 | factory(child_name, child_options.merge(parent: parent_factory), &child_block) 25 | end 26 | end 27 | 28 | def sequence(name, ...) 29 | Internal.register_sequence(Sequence.new(name, ...)) 30 | end 31 | 32 | def trait(name, &block) 33 | Internal.register_trait(Trait.new(name, &block)) 34 | end 35 | 36 | def self.run(block) 37 | new.instance_eval(&block) 38 | end 39 | 40 | delegate :after, 41 | :before, 42 | :callback, 43 | :initialize_with, 44 | :skip_create, 45 | :to_create, 46 | to: FactoryBot::Internal 47 | end 48 | 49 | class ModifyDSL 50 | def factory(name, _options = {}, &block) 51 | factory = Internal.factory_by_name(name) 52 | proxy = FactoryBot::DefinitionProxy.new(factory.definition.overridable) 53 | proxy.instance_eval(&block) 54 | end 55 | 56 | def self.run(block) 57 | new.instance_eval(&block) 58 | end 59 | end 60 | end 61 | end 62 | 63 | extend Syntax::Default 64 | end 65 | -------------------------------------------------------------------------------- /docs/src/associations/build-strategies.md: -------------------------------------------------------------------------------- 1 | # Build strategies 2 | 3 | Associations default to using the same build strategy as their parent object: 4 | 5 | ```ruby 6 | FactoryBot.define do 7 | factory :author 8 | 9 | factory :post do 10 | author 11 | end 12 | end 13 | 14 | post = build(:post) 15 | post.new_record? # => true 16 | post.author.new_record? # => true 17 | 18 | post = create(:post) 19 | post.new_record? # => false 20 | post.author.new_record? # => false 21 | ``` 22 | 23 | This is different than the default behavior for previous versions of 24 | factory\_bot, where the association strategy would not always match the strategy 25 | of the parent object. If you want to continue using the old behavior, you can 26 | set the `use_parent_strategy` configuration option to `false`. 27 | 28 | ```ruby 29 | FactoryBot.use_parent_strategy = false 30 | 31 | # Builds and saves a User and a Post 32 | post = create(:post) 33 | post.new_record? # => false 34 | post.author.new_record? # => false 35 | 36 | # Builds and saves a User, and then builds but does not save a Post 37 | post = build(:post) 38 | post.new_record? # => true 39 | post.author.new_record? # => false 40 | ``` 41 | 42 | To not save the associated object, specify `strategy: :build` in the factory: 43 | 44 | ```ruby 45 | FactoryBot.use_parent_strategy = false 46 | 47 | factory :post do 48 | # ... 49 | association :author, factory: :user, strategy: :build 50 | end 51 | 52 | # Builds a User, and then builds a Post, but does not save either 53 | post = build(:post) 54 | post.new_record? # => true 55 | post.author.new_record? # => true 56 | ``` 57 | 58 | Note that the `strategy: :build` option must be passed to an explicit call to 59 | `association`, and cannot be used with implicit associations: 60 | 61 | ```ruby 62 | factory :post do 63 | # ... 64 | author strategy: :build # <<< this does *not* work; causes author_id to be nil 65 | ``` 66 | -------------------------------------------------------------------------------- /spec/acceptance/parent_spec.rb: -------------------------------------------------------------------------------- 1 | describe "an instance generated by a factory that inherits from another factory" do 2 | before do 3 | define_model("User", name: :string, admin: :boolean, email: :string, upper_email: :string, login: :string) 4 | 5 | FactoryBot.define do 6 | factory :user do 7 | name { "John" } 8 | email { "#{name.downcase}@example.com" } 9 | login { email } 10 | 11 | factory :admin do 12 | name { "admin" } 13 | admin { true } 14 | upper_email { email.upcase } 15 | end 16 | end 17 | end 18 | end 19 | 20 | describe "the parent class" do 21 | subject { FactoryBot.create(:user) } 22 | it { should_not be_admin } 23 | its(:name) { should eq "John" } 24 | its(:email) { should eq "john@example.com" } 25 | its(:login) { should eq "john@example.com" } 26 | end 27 | 28 | describe "the child class redefining parent's attributes" do 29 | subject { FactoryBot.create(:admin) } 30 | it { should be_kind_of(User) } 31 | it { should be_admin } 32 | its(:name) { should eq "admin" } 33 | its(:email) { should eq "admin@example.com" } 34 | its(:login) { should eq "admin@example.com" } 35 | its(:upper_email) { should eq "ADMIN@EXAMPLE.COM" } 36 | end 37 | end 38 | 39 | describe "nested factories with different parents" do 40 | before do 41 | define_model("User", name: :string) 42 | 43 | FactoryBot.define do 44 | factory :user do 45 | name { "Basic User" } 46 | 47 | factory :male_user do 48 | name { "John Doe" } 49 | end 50 | 51 | factory :uppercase_male_user, parent: :male_user do 52 | after(:build) { |user| user.name = user.name.upcase } 53 | end 54 | end 55 | end 56 | end 57 | 58 | it "honors :parent over the factory block nesting" do 59 | expect(FactoryBot.build(:uppercase_male_user).name).to eq "JOHN DOE" 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/support/matchers/declaration.rb: -------------------------------------------------------------------------------- 1 | module DeclarationMatchers 2 | def have_dynamic_declaration(name) 3 | DeclarationMatcher.new(:dynamic).named(name) 4 | end 5 | 6 | def have_association_declaration(name) 7 | DeclarationMatcher.new(:association).named(name) 8 | end 9 | 10 | def have_implicit_declaration(name) 11 | DeclarationMatcher.new(:implicit).named(name) 12 | end 13 | 14 | class DeclarationMatcher 15 | def initialize(declaration_type) 16 | @declaration_type = declaration_type 17 | end 18 | 19 | def matches?(subject) 20 | subject.declarations.include?(expected_declaration) 21 | end 22 | 23 | def named(name) 24 | @name = name 25 | self 26 | end 27 | 28 | def ignored 29 | @ignored = true 30 | self 31 | end 32 | 33 | def with_value(value) 34 | @value = value 35 | self 36 | end 37 | 38 | def with_factory(factory) 39 | @factory = factory 40 | self 41 | end 42 | 43 | def with_options(options) 44 | @options = options 45 | self 46 | end 47 | 48 | def failure_message 49 | [ 50 | "expected declarations to include declaration of type #{@declaration_type}", 51 | @options ? "with options #{options}" : nil 52 | ].compact.join " " 53 | end 54 | 55 | private 56 | 57 | def expected_declaration 58 | case @declaration_type 59 | when :dynamic then FactoryBot::Declaration::Dynamic.new(@name, ignored?, @value) 60 | when :implicit then FactoryBot::Declaration::Implicit.new(@name, @factory, ignored?) 61 | when :association 62 | if @options 63 | FactoryBot::Declaration::Association.new(@name, options) 64 | else 65 | FactoryBot::Declaration::Association.new(@name) 66 | end 67 | end 68 | end 69 | 70 | def ignored? 71 | !!@ignored 72 | end 73 | 74 | def options 75 | @options || {} 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /docs/src/ref/traits_for_enum.md: -------------------------------------------------------------------------------- 1 | # traits_for_enum 2 | 3 | With a `factory` definition block, the `traits_for_enum` method is a helper for 4 | any object with an attribute that can be one of a few values. The original 5 | inspiration was [`ActiveRecord::Enum`] but it can apply to any attribute with a 6 | restricted set of values. 7 | 8 | [`ActiveRecord::Enum`]: https://api.rubyonrails.org/classes/ActiveRecord/Enum.html 9 | 10 | This method creates a trait for each value. 11 | 12 | The `traits_for_enum` method takes a required attribute name and an optional 13 | set of values. The values can be any Enumerable, such as Array or Hash. By 14 | default, the values are `nil`. 15 | 16 | If the values are an Array, this method defines a trait for each element in the 17 | array. The trait's name is the array element, and it sets the attribute to the 18 | same array element. 19 | 20 | If the values are a Hash, this method defines traits based on the keys, 21 | setting the attribute to the values. The trait's name is the key, and it sets 22 | the attribute to the value. 23 | 24 | If the value is any other Enumerable, it treats it like an Array or Hash based 25 | on whether `#each` iterates in pairs like it does for Hashes. 26 | 27 | If the value is nil, it uses a class method named after the pluralized 28 | attribute name. 29 | 30 | ```ruby 31 | FactoryBot.define do 32 | factory :article do 33 | traits_for_enum :visibility, [:public, :private] 34 | # trait :public do 35 | # visibility { :public } 36 | # end 37 | # trait :private do 38 | # visibility { :private } 39 | # end 40 | 41 | traits_for_enum :collaborative, draft: 0, shared: 1 42 | # trait :draft do 43 | # collaborative { 0 } 44 | # end 45 | # trait :shared do 46 | # collaborative { 1 } 47 | # end 48 | 49 | traits_for_enum :status 50 | # Article.statuses.each do |key, value| 51 | # trait key do 52 | # status { value } 53 | # end 54 | # end 55 | end 56 | end 57 | ``` 58 | -------------------------------------------------------------------------------- /spec/acceptance/build_list_spec.rb: -------------------------------------------------------------------------------- 1 | describe "build multiple instances" do 2 | before do 3 | define_model("Post", title: :string, position: :integer) 4 | 5 | FactoryBot.define do 6 | factory(:post) do |post| 7 | post.title { "Through the Looking Glass" } 8 | post.position { rand(10**4) } 9 | end 10 | end 11 | end 12 | 13 | context "without default attributes" do 14 | subject { FactoryBot.build_list(:post, 20) } 15 | 16 | its(:length) { should eq 20 } 17 | 18 | it "builds (but doesn't save) all the posts" do 19 | subject.each do |record| 20 | expect(record).to be_new_record 21 | end 22 | end 23 | 24 | it "uses the default factory values" do 25 | subject.each do |record| 26 | expect(record.title).to eq "Through the Looking Glass" 27 | end 28 | end 29 | end 30 | 31 | context "with default attributes" do 32 | subject { FactoryBot.build_list(:post, 20, title: "The Hunting of the Snark") } 33 | 34 | it "overrides the default values" do 35 | subject.each do |record| 36 | expect(record.title).to eq "The Hunting of the Snark" 37 | end 38 | end 39 | end 40 | 41 | context "with a block" do 42 | subject do 43 | FactoryBot.build_list(:post, 20, title: "The Listing of the Block") do |post| 44 | post.position = post.id 45 | end 46 | end 47 | 48 | it "correctly uses the set value" do 49 | subject.each do |record| 50 | expect(record.position).to eq record.id 51 | end 52 | end 53 | end 54 | 55 | context "with a block that receives both the object and an index" do 56 | subject do 57 | FactoryBot.build_list(:post, 20, title: "The Indexed Block") do |post, index| 58 | post.position = index 59 | end 60 | end 61 | 62 | it "correctly uses the set value" do 63 | subject.each_with_index do |record, index| 64 | expect(record.position).to eq index 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /docs/src/traits/enum.md: -------------------------------------------------------------------------------- 1 | # Enum traits 2 | 3 | Given an Active Record model with an enum attribute: 4 | 5 | ```rb 6 | class Task < ActiveRecord::Base 7 | enum status: {queued: 0, started: 1, finished: 2} 8 | end 9 | 10 | ``` 11 | 12 | factory\_bot will automatically define traits for each possible value of the 13 | enum: 14 | 15 | ```rb 16 | FactoryBot.define do 17 | factory :task 18 | end 19 | 20 | FactoryBot.build(:task, :queued) 21 | FactoryBot.build(:task, :started) 22 | FactoryBot.build(:task, :finished) 23 | ``` 24 | 25 | Writing the traits out manually would be cumbersome, and is not necessary: 26 | 27 | ```rb 28 | FactoryBot.define do 29 | factory :task do 30 | trait :queued do 31 | status { :queued } 32 | end 33 | 34 | trait :started do 35 | status { :started } 36 | end 37 | 38 | trait :finished do 39 | status { :finished } 40 | end 41 | end 42 | end 43 | ``` 44 | 45 | If automatically defining traits for enum attributes on every factory is not 46 | desired, it is possible to disable the feature by setting 47 | `FactoryBot.automatically_define_enum_traits = false` 48 | 49 | In that case, it is still possible to explicitly define traits for an enum 50 | attribute in a particular factory: 51 | 52 | ```rb 53 | FactoryBot.automatically_define_enum_traits = false 54 | 55 | FactoryBot.define do 56 | factory :task do 57 | traits_for_enum(:status) 58 | end 59 | end 60 | ``` 61 | 62 | It is also possible to use this feature for other enumerable values, not 63 | specifically tied to Active Record enum attributes. 64 | 65 | With an array: 66 | 67 | ```rb 68 | class Task 69 | attr_accessor :status 70 | end 71 | 72 | FactoryBot.define do 73 | factory :task do 74 | traits_for_enum(:status, ["queued", "started", "finished"]) 75 | end 76 | end 77 | ``` 78 | 79 | Or with a hash: 80 | 81 | ```rb 82 | class Task 83 | attr_accessor :status 84 | end 85 | 86 | FactoryBot.define do 87 | factory :task do 88 | traits_for_enum(:status, { queued: 0, started: 1, finished: 2 }) 89 | end 90 | end 91 | ``` 92 | -------------------------------------------------------------------------------- /spec/acceptance/attribute_existing_on_object_spec.rb: -------------------------------------------------------------------------------- 1 | describe "declaring attributes on a Factory that are private methods on Object" do 2 | before do 3 | define_model("Website", system: :boolean, link: :string, sleep: :integer) 4 | 5 | FactoryBot.define do 6 | factory :website do 7 | system { false } 8 | link { "http://example.com" } 9 | sleep { 15 } 10 | end 11 | end 12 | end 13 | 14 | subject { FactoryBot.build(:website, sleep: -5) } 15 | 16 | its(:system) { should eq false } 17 | its(:link) { should eq "http://example.com" } 18 | its(:sleep) { should eq(-5) } 19 | end 20 | 21 | describe "assigning overrides that are also private methods on object" do 22 | before do 23 | define_model("Website", format: :string, y: :integer, more_format: :string, some_funky_method: :string) 24 | 25 | Object.class_eval do 26 | private 27 | 28 | def some_funky_method(args) 29 | end 30 | end 31 | 32 | FactoryBot.define do 33 | factory :website do 34 | more_format { "format: #{format}" } 35 | end 36 | end 37 | end 38 | 39 | after do 40 | Object.send(:undef_method, :some_funky_method) 41 | end 42 | 43 | subject { FactoryBot.build(:website, format: "Great", y: 12345, some_funky_method: "foobar!") } 44 | its(:format) { should eq "Great" } 45 | its(:y) { should eq 12345 } 46 | its(:more_format) { should eq "format: Great" } 47 | its(:some_funky_method) { should eq "foobar!" } 48 | end 49 | 50 | describe "accessing methods from the instance within a dynamic attribute " \ 51 | "that is also a private method on object" do 52 | before do 53 | define_model("Website", more_format: :string) do 54 | def format 55 | "This is an awesome format" 56 | end 57 | end 58 | 59 | FactoryBot.define do 60 | factory :website do 61 | more_format { "format: #{format}" } 62 | end 63 | end 64 | end 65 | 66 | subject { FactoryBot.build(:website) } 67 | its(:more_format) { should eq "format: This is an awesome format" } 68 | end 69 | -------------------------------------------------------------------------------- /lib/factory_bot/strategy_syntax_method_registrar.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | class StrategySyntaxMethodRegistrar 4 | def initialize(strategy_name) 5 | @strategy_name = strategy_name 6 | end 7 | 8 | def define_strategy_methods 9 | define_singular_strategy_method 10 | define_list_strategy_method 11 | define_pair_strategy_method 12 | end 13 | 14 | def self.with_index(block, index) 15 | if block&.arity == 2 16 | ->(instance) { block.call(instance, index) } 17 | else 18 | block 19 | end 20 | end 21 | 22 | private 23 | 24 | def define_singular_strategy_method 25 | strategy_name = @strategy_name 26 | 27 | define_syntax_method(strategy_name) do |name, *traits_and_overrides, &block| 28 | FactoryRunner.new(name, strategy_name, traits_and_overrides).run(&block) 29 | end 30 | end 31 | 32 | def define_list_strategy_method 33 | strategy_name = @strategy_name 34 | 35 | define_syntax_method("#{strategy_name}_list") do |name, amount, *traits_and_overrides, &block| 36 | unless amount.respond_to?(:times) 37 | raise ArgumentError, "count missing for #{strategy_name}_list" 38 | end 39 | 40 | Array.new(amount) do |i| 41 | block_with_index = StrategySyntaxMethodRegistrar.with_index(block, i) 42 | send(strategy_name, name, *traits_and_overrides, &block_with_index) 43 | end 44 | end 45 | end 46 | 47 | def define_pair_strategy_method 48 | strategy_name = @strategy_name 49 | 50 | define_syntax_method("#{strategy_name}_pair") do |name, *traits_and_overrides, &block| 51 | Array.new(2) { send(strategy_name, name, *traits_and_overrides, &block) } 52 | end 53 | end 54 | 55 | def define_syntax_method(name, &block) 56 | FactoryBot::Syntax::Methods.module_exec do 57 | if method_defined?(name) || private_method_defined?(name) 58 | undef_method(name) 59 | end 60 | 61 | define_method(name, &block) 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/factory_bot/attribute/dynamic_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Attribute::Dynamic do 2 | let(:name) { :first_name } 3 | let(:block) { -> {} } 4 | 5 | subject { FactoryBot::Attribute::Dynamic.new(name, false, block) } 6 | 7 | its(:name) { should eq name } 8 | 9 | context "with a block returning a static value" do 10 | let(:block) { -> { "value" } } 11 | 12 | it "returns the value when executing the proc" do 13 | expect(subject.to_proc.call).to eq "value" 14 | end 15 | end 16 | 17 | context "with a block returning its block-level variable" do 18 | let(:block) { ->(thing) { thing } } 19 | 20 | it "returns self when executing the proc" do 21 | expect(subject.to_proc.call).to eq subject 22 | end 23 | end 24 | 25 | context "with a block referencing an attribute on the attribute" do 26 | let(:block) { -> { attribute_defined_on_attribute } } 27 | let(:result) { "other attribute value" } 28 | 29 | before do 30 | # Define an '#attribute_defined_on_attribute' instance method allowing it 31 | # be mocked. Usually this is determined via '#method_missing' 32 | missing_methods = Module.new { 33 | def attribute_defined_on_attribute(*args) 34 | end 35 | } 36 | subject.extend(missing_methods) 37 | 38 | allow(subject) 39 | .to receive(:attribute_defined_on_attribute).and_return result 40 | end 41 | 42 | it "evaluates the attribute from the attribute" do 43 | expect(subject.to_proc.call).to eq result 44 | end 45 | end 46 | 47 | context "with a block returning a sequence" do 48 | let(:block) do 49 | -> do 50 | FactoryBot::Internal.register_sequence( 51 | FactoryBot::Sequence.new(:email, 1) { |n| "foo#{n}" } 52 | ) 53 | end 54 | end 55 | 56 | it "raises a sequence abuse error" do 57 | expect { subject.to_proc.call }.to raise_error(FactoryBot::SequenceAbuseError) 58 | end 59 | end 60 | end 61 | 62 | describe FactoryBot::Attribute::Dynamic, "with a string name" do 63 | subject { FactoryBot::Attribute::Dynamic.new("name", false, -> {}) } 64 | its(:name) { should eq :name } 65 | end 66 | -------------------------------------------------------------------------------- /spec/acceptance/sequence_spec.rb: -------------------------------------------------------------------------------- 1 | describe "sequences" do 2 | include FactoryBot::Syntax::Methods 3 | 4 | it "generates several values in the correct format" do 5 | FactoryBot.define do 6 | sequence :email do |n| 7 | "somebody#{n}@example.com" 8 | end 9 | end 10 | 11 | first_value = generate(:email) 12 | another_value = generate(:email) 13 | 14 | expect(first_value).to match(/^somebody\d+@example\.com$/) 15 | expect(another_value).to match(/^somebody\d+@example\.com$/) 16 | expect(first_value).not_to eq another_value 17 | end 18 | 19 | it "generates sequential numbers if no block is given" do 20 | FactoryBot.define do 21 | sequence :order 22 | end 23 | 24 | first_value = generate(:order) 25 | another_value = generate(:order) 26 | 27 | expect(first_value).to eq 1 28 | expect(another_value).to eq 2 29 | expect(first_value).not_to eq another_value 30 | end 31 | 32 | it "generates aliases for the sequence that reference the same block" do 33 | FactoryBot.define do 34 | sequence(:size, aliases: [:count, :length]) { |n| "called-#{n}" } 35 | end 36 | 37 | first_value = generate(:size) 38 | second_value = generate(:count) 39 | third_value = generate(:length) 40 | 41 | expect(first_value).to eq "called-1" 42 | expect(second_value).to eq "called-2" 43 | expect(third_value).to eq "called-3" 44 | end 45 | 46 | it "generates aliases for the sequence that reference the same block and retains value" do 47 | FactoryBot.define do 48 | sequence(:size, "a", aliases: [:count, :length]) { |n| "called-#{n}" } 49 | end 50 | 51 | first_value = generate(:size) 52 | second_value = generate(:count) 53 | third_value = generate(:length) 54 | 55 | expect(first_value).to eq "called-a" 56 | expect(second_value).to eq "called-b" 57 | expect(third_value).to eq "called-c" 58 | end 59 | 60 | it "generates few values of the sequence" do 61 | FactoryBot.define do 62 | sequence :email do |n| 63 | "somebody#{n}@example.com" 64 | end 65 | end 66 | 67 | values = generate_list(:email, 2) 68 | 69 | expect(values.first).to eq("somebody1@example.com") 70 | expect(values.second).to eq("somebody2@example.com") 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /docs/src/ref/method_missing.md: -------------------------------------------------------------------------------- 1 | # method_missing 2 | 3 | With a `factory` definition block, you can use `add_attribute`, `association`, 4 | `sequence`, and `trait` to define a factory. You can also level a default 5 | `method_missing` definition for potential shortcuts. 6 | 7 | Calling an unknown method (e.g. `name`, `admin`, `email`, `account`) connects 8 | an association, sequence, trait, or attribute to the factory: 9 | 10 | 1. If the method missing is passed a block, it always defines an attribute. 11 | This allows you to set the value for the attribute. 12 | 13 | 1. If the method missing is passed a hash as a argument with the key 14 | `:factory`, then it always defines an association. This allows you to 15 | override the factory used for the association. 16 | 17 | 1. If there is another factory of the same name, then it defines an 18 | association. 19 | 20 | 1. If there is a global sequence of the same name, then it defines an attribute 21 | with a value that pulls from the sequence. 22 | 23 | 1. If there is a trait of the same name for that factory, then it turns that 24 | trait on for all builds of this factory. 25 | 26 | Using `method_missing` can turn an explicit definition: 27 | 28 | ```ruby 29 | FactoryBot.define do 30 | sequence(:email) { |n| "person#{n}@example.com" } 31 | factory :account 32 | factory :organization 33 | 34 | factory :user, traits: [:admin] do 35 | add_attribute(:name) { "Lord Nikon" } 36 | add_attribute(:email) { generate(:email) } 37 | association :account 38 | association :org, factory: :organization 39 | 40 | trait :admin do 41 | add_attribute(:admin) { true } 42 | end 43 | end 44 | end 45 | ``` 46 | 47 | ... into a more implicit definition: 48 | 49 | ```ruby 50 | FactoryBot.define do 51 | sequence(:email) { |n| "person#{n}@example.com" } 52 | factory :account 53 | factory :organization 54 | 55 | factory :user do 56 | name { "Lord Nikon" } # no more `add_attribute` 57 | admin # no more :traits 58 | email # no more `add_attribute` 59 | account # no more `association` 60 | org factory: :organization # no more `association` 61 | 62 | trait :admin do 63 | admin { true } 64 | end 65 | end 66 | end 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/src/activesupport-instrumentation/summary.md: -------------------------------------------------------------------------------- 1 | # ActiveSupport Instrumentation 2 | 3 | In order to track what factories are created (and with what build strategy), 4 | `ActiveSupport::Notifications` are included to provide a way to subscribe to 5 | factories being compiled and run. One example would be to track factories based on a 6 | threshold of execution time. 7 | 8 | ```ruby 9 | ActiveSupport::Notifications.subscribe("factory_bot.run_factory") do |name, start, finish, id, payload| 10 | execution_time_in_seconds = finish - start 11 | 12 | if execution_time_in_seconds >= 0.5 13 | $stderr.puts "Slow factory: #{payload[:name]} using strategy #{payload[:strategy]}" 14 | end 15 | end 16 | ``` 17 | 18 | Another example would be tracking all factories and how they're used throughout 19 | your test suite. If you're using RSpec, it's as simple as adding a 20 | `before(:suite)` and `after(:suite)`: 21 | 22 | ```ruby 23 | factory_bot_results = {} 24 | config.before(:suite) do 25 | ActiveSupport::Notifications.subscribe("factory_bot.run_factory") do |name, start, finish, id, payload| 26 | factory_name = payload[:name] 27 | strategy_name = payload[:strategy] 28 | factory_bot_results[factory_name] ||= {} 29 | factory_bot_results[factory_name][strategy_name] ||= 0 30 | factory_bot_results[factory_name][strategy_name] += 1 31 | end 32 | end 33 | 34 | config.after(:suite) do 35 | puts factory_bot_results 36 | end 37 | ``` 38 | 39 | Another example could involve tracking the attributes and traits that factories are compiled with. If you're using RSpec, you could add `before(:suite)` and `after(:suite)` blocks that subscribe to `factory_bot.compile_factory` notifications: 40 | 41 | ```ruby 42 | factory_bot_results = {} 43 | config.before(:suite) do 44 | ActiveSupport::Notifications.subscribe("factory_bot.compile_factory") do |name, start, finish, id, payload| 45 | factory_name = payload[:name] 46 | factory_class = payload[:class] 47 | attributes = payload[:attributes] 48 | traits = payload[:traits] 49 | factory_bot_results[factory_class] ||= {} 50 | factory_bot_results[factory_class][factory_name] = { 51 | attributes: attributes.map(&:name) 52 | traits: traits.map(&:name) 53 | } 54 | end 55 | end 56 | 57 | config.after(:suite) do 58 | puts factory_bot_results 59 | end 60 | ``` 61 | -------------------------------------------------------------------------------- /spec/acceptance/global_initialize_with_spec.rb: -------------------------------------------------------------------------------- 1 | describe "global initialize_with" do 2 | before do 3 | define_class("User") do 4 | attr_accessor :name 5 | 6 | def initialize(name) 7 | @name = name 8 | end 9 | end 10 | 11 | define_class("Post") do 12 | attr_reader :name 13 | 14 | def initialize(name) 15 | @name = name 16 | end 17 | end 18 | 19 | FactoryBot.define do 20 | initialize_with { new("initialize_with") } 21 | 22 | trait :with_initialize_with do 23 | initialize_with { new("trait initialize_with") } 24 | end 25 | 26 | factory :user do 27 | factory :child_user 28 | 29 | factory :child_user_with_trait do 30 | with_initialize_with 31 | end 32 | end 33 | 34 | factory :post do 35 | factory :child_post 36 | 37 | factory :child_post_with_trait do 38 | with_initialize_with 39 | end 40 | end 41 | end 42 | end 43 | 44 | it "handles base initialize_with" do 45 | expect(FactoryBot.build(:user).name).to eq "initialize_with" 46 | expect(FactoryBot.build(:post).name).to eq "initialize_with" 47 | end 48 | 49 | it "handles child initialize_with" do 50 | expect(FactoryBot.build(:child_user).name).to eq "initialize_with" 51 | expect(FactoryBot.build(:child_post).name).to eq "initialize_with" 52 | end 53 | 54 | it "handles child initialize_with with trait" do 55 | expect(FactoryBot.build(:child_user_with_trait).name).to eq "trait initialize_with" 56 | expect(FactoryBot.build(:child_post_with_trait).name).to eq "trait initialize_with" 57 | end 58 | 59 | it "handles inline trait override" do 60 | expect(FactoryBot.build(:child_user, :with_initialize_with).name).to eq "trait initialize_with" 61 | expect(FactoryBot.build(:child_post, :with_initialize_with).name).to eq "trait initialize_with" 62 | end 63 | 64 | it "uses initialize_with globally across FactoryBot.define" do 65 | define_class("Company") do 66 | attr_reader :name 67 | 68 | def initialize(name) 69 | @name = name 70 | end 71 | end 72 | 73 | FactoryBot.define do 74 | factory :company 75 | end 76 | 77 | expect(FactoryBot.build(:company).name).to eq "initialize_with" 78 | expect(FactoryBot.build(:company, :with_initialize_with).name).to eq "trait initialize_with" 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /docs/src/linting-factories/summary.md: -------------------------------------------------------------------------------- 1 | # Linting Factories 2 | 3 | factory\_bot allows for linting known factories: 4 | 5 | ```ruby 6 | FactoryBot.lint 7 | ``` 8 | 9 | `FactoryBot.lint` creates each factory and catches any exceptions raised during 10 | the creation process. `FactoryBot::InvalidFactoryError` is raised with a list 11 | of factories (and corresponding exceptions) for factories which could not be 12 | created. 13 | 14 | Recommended usage of `FactoryBot.lint` is to run this in a separate task before 15 | your test suite is executed. Running it in a `before(:suite)` will negatively 16 | impact the performance of your tests when running single tests. 17 | 18 | Example Rake task: 19 | 20 | ```ruby 21 | # lib/tasks/factory_bot.rake 22 | namespace :factory_bot do 23 | desc "Verify that all FactoryBot factories are valid" 24 | task lint: :environment do 25 | if Rails.env.test? 26 | conn = ActiveRecord::Base.connection 27 | conn.transaction do 28 | FactoryBot.lint 29 | raise ActiveRecord::Rollback 30 | end 31 | else 32 | system("bundle exec rake factory_bot:lint RAILS_ENV='test'") 33 | fail if $?.exitstatus.nonzero? 34 | end 35 | end 36 | end 37 | ``` 38 | 39 | After calling `FactoryBot.lint`, you'll likely want to clear out the database, 40 | as records will most likely be created. The provided example above uses an SQL 41 | transaction and rollback to leave the database clean. 42 | 43 | You can lint factories selectively by passing only factories you want linted: 44 | 45 | ```ruby 46 | factories_to_lint = FactoryBot.factories.reject do |factory| 47 | factory.name =~ /^old_/ 48 | end 49 | 50 | FactoryBot.lint factories_to_lint 51 | ``` 52 | 53 | This would lint all factories that aren't prefixed with `old_`. 54 | 55 | Traits can also be linted. This option verifies that each and every trait of a 56 | factory generates a valid object on its own. This is turned on by passing 57 | `traits: true` to the `lint` method: 58 | 59 | ```ruby 60 | FactoryBot.lint traits: true 61 | ``` 62 | 63 | This can also be combined with other arguments: 64 | 65 | ```ruby 66 | FactoryBot.lint factories_to_lint, traits: true 67 | ``` 68 | 69 | You can also specify the strategy used for linting: 70 | 71 | ```ruby 72 | FactoryBot.lint strategy: :build 73 | ``` 74 | 75 | Verbose linting will include full backtraces for each error, which can be 76 | helpful for debugging: 77 | 78 | ```ruby 79 | FactoryBot.lint verbose: true 80 | ``` 81 | -------------------------------------------------------------------------------- /lib/factory_bot/evaluator.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/hash/except" 2 | require "active_support/core_ext/class/attribute" 3 | 4 | module FactoryBot 5 | # @api private 6 | class Evaluator 7 | class_attribute :attribute_lists 8 | 9 | private_instance_methods.each do |method| 10 | undef_method(method) unless method.match?(/^__|initialize/) 11 | end 12 | 13 | def initialize(build_strategy, overrides = {}) 14 | @build_strategy = build_strategy 15 | @overrides = overrides 16 | @cached_attributes = overrides 17 | @instance = nil 18 | 19 | @overrides.each do |name, value| 20 | singleton_class.define_attribute(name) { value } 21 | end 22 | end 23 | 24 | def association(factory_name, *traits_and_overrides) 25 | overrides = traits_and_overrides.extract_options! 26 | strategy_override = overrides.fetch(:strategy) { 27 | FactoryBot.use_parent_strategy ? @build_strategy.to_sym : :create 28 | } 29 | 30 | traits_and_overrides += [overrides.except(:strategy)] 31 | 32 | runner = FactoryRunner.new(factory_name, strategy_override, traits_and_overrides) 33 | @build_strategy.association(runner) 34 | end 35 | 36 | attr_accessor :instance 37 | 38 | def method_missing(method_name, ...) 39 | if @instance.respond_to?(method_name) 40 | @instance.send(method_name, ...) 41 | else 42 | SyntaxRunner.new.send(method_name, ...) 43 | end 44 | end 45 | 46 | def respond_to_missing?(method_name, _include_private = false) 47 | @instance.respond_to?(method_name) || SyntaxRunner.new.respond_to?(method_name) 48 | end 49 | 50 | def __override_names__ 51 | @overrides.keys 52 | end 53 | 54 | def increment_sequence(sequence) 55 | sequence.next(self) 56 | end 57 | 58 | def self.attribute_list 59 | AttributeList.new.tap do |list| 60 | attribute_lists.each do |attribute_list| 61 | list.apply_attributes attribute_list.to_a 62 | end 63 | end 64 | end 65 | 66 | def self.define_attribute(name, &block) 67 | if instance_methods(false).include?(name) || private_instance_methods(false).include?(name) 68 | undef_method(name) 69 | end 70 | 71 | define_method(name) do 72 | if @cached_attributes.key?(name) 73 | @cached_attributes[name] 74 | else 75 | @cached_attributes[name] = instance_exec(&block) 76 | end 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/acceptance/build_spec.rb: -------------------------------------------------------------------------------- 1 | describe "a built instance" do 2 | include FactoryBot::Syntax::Methods 3 | 4 | before do 5 | define_model("User") 6 | 7 | define_model("Post", user_id: :integer) do 8 | belongs_to :user 9 | end 10 | 11 | FactoryBot.define do 12 | factory :user 13 | 14 | factory :post do 15 | user 16 | end 17 | end 18 | end 19 | 20 | subject { build(:post) } 21 | 22 | it { should be_new_record } 23 | 24 | context "when the :use_parent_strategy config option is set to false" do 25 | it "assigns and saves associations" do 26 | with_temporary_assignment(FactoryBot, :use_parent_strategy, false) do 27 | expect(subject.user).to be_kind_of(User) 28 | expect(subject.user).not_to be_new_record 29 | end 30 | end 31 | end 32 | 33 | context "when the :use_parent_strategy config option is set to true" do 34 | it "assigns but does not save associations" do 35 | with_temporary_assignment(FactoryBot, :use_parent_strategy, true) do 36 | expect(subject.user).to be_kind_of(User) 37 | expect(subject.user).to be_new_record 38 | end 39 | end 40 | end 41 | end 42 | 43 | describe "a built instance with strategy: :create" do 44 | include FactoryBot::Syntax::Methods 45 | 46 | before do 47 | define_model("User") 48 | 49 | define_model("Post", user_id: :integer) do 50 | belongs_to :user 51 | end 52 | 53 | FactoryBot.define do 54 | factory :user 55 | 56 | factory :post do 57 | association(:user, strategy: :create) 58 | end 59 | end 60 | end 61 | 62 | subject { build(:post) } 63 | 64 | it { should be_new_record } 65 | 66 | it "assigns and saves associations" do 67 | expect(subject.user).to be_kind_of(User) 68 | expect(subject.user).not_to be_new_record 69 | end 70 | end 71 | 72 | describe "calling `build` with a block" do 73 | include FactoryBot::Syntax::Methods 74 | 75 | before do 76 | define_model("Company", name: :string) 77 | 78 | FactoryBot.define do 79 | factory :company 80 | end 81 | end 82 | 83 | it "passes the built instance" do 84 | build(:company, name: "thoughtbot") do |company| 85 | expect(company.name).to eq("thoughtbot") 86 | end 87 | end 88 | 89 | it "returns the built instance" do 90 | expected = nil 91 | result = build(:company) { |company| 92 | expected = company 93 | "hello!" 94 | } 95 | expect(result).to eq expected 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/factory_bot/internal.rb: -------------------------------------------------------------------------------- 1 | module FactoryBot 2 | # @api private 3 | module Internal 4 | class << self 5 | delegate :after, 6 | :before, 7 | :callbacks, 8 | :constructor, 9 | :factories, 10 | :initialize_with, 11 | :inline_sequences, 12 | :sequences, 13 | :skip_create, 14 | :strategies, 15 | :to_create, 16 | :traits, 17 | to: :configuration 18 | 19 | def configuration 20 | @configuration ||= Configuration.new 21 | end 22 | 23 | def reset_configuration 24 | @configuration = nil 25 | end 26 | 27 | def register_inline_sequence(sequence) 28 | inline_sequences.push(sequence) 29 | end 30 | 31 | def rewind_inline_sequences 32 | inline_sequences.each(&:rewind) 33 | end 34 | 35 | def register_trait(trait) 36 | trait.names.each do |name| 37 | traits.register(name, trait) 38 | end 39 | trait 40 | end 41 | 42 | def trait_by_name(name, klass) 43 | traits.find(name).tap { |t| t.klass = klass } 44 | end 45 | 46 | def register_sequence(sequence) 47 | sequence.names.each do |name| 48 | sequences.register(name, sequence) 49 | end 50 | sequence 51 | end 52 | 53 | def sequence_by_name(name) 54 | sequences.find(name) 55 | end 56 | 57 | def rewind_sequences 58 | sequences.each(&:rewind) 59 | rewind_inline_sequences 60 | end 61 | 62 | def register_factory(factory) 63 | factory.names.each do |name| 64 | factories.register(name, factory) 65 | end 66 | factory 67 | end 68 | 69 | def factory_by_name(name) 70 | factories.find(name) 71 | end 72 | 73 | def register_strategy(strategy_name, strategy_class) 74 | strategies.register(strategy_name, strategy_class) 75 | StrategySyntaxMethodRegistrar.new(strategy_name).define_strategy_methods 76 | end 77 | 78 | def strategy_by_name(name) 79 | strategies.find(name) 80 | end 81 | 82 | def register_default_strategies 83 | register_strategy(:build, FactoryBot::Strategy::Build) 84 | register_strategy(:create, FactoryBot::Strategy::Create) 85 | register_strategy(:attributes_for, FactoryBot::Strategy::AttributesFor) 86 | register_strategy(:build_stubbed, FactoryBot::Strategy::Stub) 87 | register_strategy(:null, FactoryBot::Strategy::Null) 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /features/find_definitions.feature: -------------------------------------------------------------------------------- 1 | Feature: FactoryBot can find factory definitions correctly 2 | Scenario: Find definitions with a path 3 | Given a file named "awesome_factories.rb" with: 4 | """ 5 | FactoryBot.define do 6 | factory :awesome_category, :class => Category do 7 | name { "awesome!!!" } 8 | end 9 | end 10 | """ 11 | When "awesome_factories.rb" is added to FactoryBot's file definitions path 12 | And I create a "awesome_category" instance from FactoryBot 13 | Then I should find the following for the last category: 14 | | name | 15 | | awesome!!! | 16 | 17 | Scenario: Find definitions with an absolute path 18 | Given a file named "awesome_factories.rb" with: 19 | """ 20 | FactoryBot.define do 21 | factory :another_awesome_category, :class => Category do 22 | name { "awesome!!!" } 23 | end 24 | end 25 | """ 26 | When "awesome_factories.rb" is added to FactoryBot's file definitions path as an absolute path 27 | And I create a "another_awesome_category" instance from FactoryBot 28 | Then I should find the following for the last category: 29 | | name | 30 | | awesome!!! | 31 | 32 | Scenario: Find definitions with a folder 33 | Given a file named "nested/great_factories.rb" with: 34 | """ 35 | FactoryBot.define do 36 | factory :great_category, :class => Category do 37 | name { "great!!!" } 38 | end 39 | end 40 | """ 41 | When "nested" is added to FactoryBot's file definitions path 42 | And I create a "great_category" instance from FactoryBot 43 | Then I should find the following for the last category: 44 | | name | 45 | | great!!! | 46 | 47 | Scenario: Reload FactoryBot 48 | Given a file named "nested/reload_factories.rb" with: 49 | """ 50 | FactoryBot.define do 51 | sequence(:great) 52 | trait :admin do 53 | admin { true } 54 | end 55 | 56 | factory :handy_category, :class => Category do 57 | name { "handy" } 58 | end 59 | end 60 | """ 61 | When "nested" is added to FactoryBot's file definitions path 62 | And I append to "nested/reload_factories.rb" with: 63 | """ 64 | 65 | FactoryBot.modify do 66 | factory :handy_category do 67 | name { "HANDY!!!" } 68 | end 69 | end 70 | """ 71 | And I reload factories 72 | And I create a "handy_category" instance from FactoryBot 73 | Then I should find the following for the last category: 74 | | name | 75 | | HANDY!!! | 76 | -------------------------------------------------------------------------------- /spec/factory_bot/evaluator_class_definer_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::EvaluatorClassDefiner do 2 | it "returns an evaluator when accessing the evaluator class" do 3 | evaluator = define_evaluator(parent_class: FactoryBot::Evaluator) 4 | 5 | expect(evaluator).to be_a(FactoryBot::Evaluator) 6 | end 7 | 8 | it "adds each attribute to the evaluator" do 9 | attribute = stub_attribute(:attribute) { 1 } 10 | evaluator = define_evaluator(attributes: [attribute]) 11 | 12 | expect(evaluator.attribute).to eq 1 13 | end 14 | 15 | it "evaluates the block in the context of the evaluator" do 16 | dependency_attribute = stub_attribute(:dependency) { 1 } 17 | attribute = stub_attribute(:attribute) { dependency + 1 } 18 | evaluator = define_evaluator(attributes: [dependency_attribute, attribute]) 19 | 20 | expect(evaluator.attribute).to eq 2 21 | end 22 | 23 | it "only instance_execs the block once even when returning nil" do 24 | count = 0 25 | attribute = stub_attribute(:attribute) { 26 | count += 1 27 | nil 28 | } 29 | evaluator = define_evaluator(attributes: [attribute]) 30 | 31 | 2.times { evaluator.attribute } 32 | 33 | expect(count).to eq 1 34 | end 35 | 36 | it "sets attributes on the evaluator class" do 37 | attributes = [stub_attribute, stub_attribute] 38 | evaluator = define_evaluator(attributes: attributes) 39 | 40 | expect(evaluator.attribute_lists).to eq [attributes] 41 | end 42 | 43 | context "with a custom evaluator as a parent class" do 44 | it "bases its attribute lists on itself and its parent evaluator" do 45 | parent_attributes = [stub_attribute, stub_attribute] 46 | parent_evaluator_class = define_evaluator_class(attributes: parent_attributes) 47 | child_attributes = [stub_attribute, stub_attribute] 48 | child_evaluator = define_evaluator( 49 | attributes: child_attributes, 50 | parent_class: parent_evaluator_class 51 | ) 52 | 53 | expect(child_evaluator.attribute_lists).to eq [parent_attributes, child_attributes] 54 | end 55 | end 56 | 57 | def define_evaluator(arguments = {}) 58 | evaluator_class = define_evaluator_class(arguments) 59 | evaluator_class.new(FactoryBot::Strategy::Null) 60 | end 61 | 62 | def define_evaluator_class(arguments = {}) 63 | evaluator_class_definer = FactoryBot::EvaluatorClassDefiner.new( 64 | arguments[:attributes] || [], 65 | arguments[:parent_class] || FactoryBot::Evaluator 66 | ) 67 | evaluator_class_definer.evaluator_class 68 | end 69 | 70 | def stub_attribute(name = :attribute, &value) 71 | value ||= -> {} 72 | double(name.to_s, name: name.to_sym, to_proc: value) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/factory_bot/definition_spec.rb: -------------------------------------------------------------------------------- 1 | describe FactoryBot::Definition do 2 | it "delegates :declare_attribute to declarations" do 3 | definition = described_class.new(:name) 4 | 5 | expect(definition).to delegate(:declare_attribute).to(:declarations) 6 | end 7 | 8 | it "creates a new attribute list with the name passed when given a name" do 9 | name = "great name" 10 | allow(FactoryBot::DeclarationList).to receive(:new) 11 | 12 | FactoryBot::Definition.new(name) 13 | 14 | expect(FactoryBot::DeclarationList).to have_received(:new).with(name) 15 | end 16 | 17 | it "has a name" do 18 | name = "factory name" 19 | definition = described_class.new(name) 20 | 21 | expect(definition.name).to eq(name) 22 | end 23 | 24 | it "has an overridable declaration list" do 25 | list = double("declaration list", overridable: true) 26 | allow(FactoryBot::DeclarationList).to receive(:new).and_return list 27 | definition = described_class.new(:name) 28 | 29 | expect(definition.overridable).to eq definition 30 | expect(list).to have_received(:overridable).once 31 | end 32 | 33 | it "maintains a list of traits" do 34 | trait1 = double(:trait) 35 | trait2 = double(:trait) 36 | definition = described_class.new(:name) 37 | definition.define_trait(trait1) 38 | definition.define_trait(trait2) 39 | 40 | expect(definition.defined_traits).to include(trait1, trait2) 41 | end 42 | 43 | it "adds only unique traits" do 44 | trait1 = double(:trait) 45 | definition = described_class.new(:name) 46 | definition.define_trait(trait1) 47 | definition.define_trait(trait1) 48 | 49 | expect(definition.defined_traits.size).to eq 1 50 | end 51 | 52 | it "maintains a list of callbacks" do 53 | callback1 = "callback1" 54 | callback2 = "callback2" 55 | definition = described_class.new(:name) 56 | definition.add_callback(callback1) 57 | definition.add_callback(callback2) 58 | 59 | expect(definition.callbacks).to eq [callback1, callback2] 60 | end 61 | 62 | it "doesn't expose a separate create strategy when none is specified" do 63 | definition = described_class.new(:name) 64 | 65 | expect(definition.to_create).to be_nil 66 | end 67 | 68 | it "exposes a non-default create strategy when one is provided by the user" do 69 | definition = described_class.new(:name) 70 | block = proc {} 71 | definition.to_create(&block) 72 | 73 | expect(definition.to_create).to eq block 74 | end 75 | 76 | it "maintains a list of enum fields" do 77 | definition = described_class.new(:name) 78 | 79 | enum_field = double("enum_field") 80 | 81 | definition.register_enum(enum_field) 82 | 83 | expect(definition.registered_enums).to include(enum_field) 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/acceptance/attributes_for_spec.rb: -------------------------------------------------------------------------------- 1 | if RUBY_ENGINE != "truffleruby" 2 | require_relative "attributes_for_destructuring" 3 | end 4 | 5 | describe "a generated attributes hash" do 6 | include FactoryBot::Syntax::Methods 7 | 8 | before do 9 | define_model("User") 10 | define_model("Comment") 11 | 12 | define_model("Post", title: :string, 13 | body: :string, 14 | summary: :string, 15 | user_id: :integer) do 16 | belongs_to :user 17 | has_many :comments 18 | end 19 | 20 | FactoryBot.define do 21 | factory :user 22 | factory :comment 23 | 24 | factory :post do 25 | title { "default title" } 26 | body { "default body" } 27 | summary { title } 28 | user 29 | comments do |c| 30 | [c.association(:comment)] 31 | end 32 | end 33 | end 34 | end 35 | 36 | subject { attributes_for(:post, title: "overridden title") } 37 | 38 | it "assigns an overridden value" do 39 | expect(subject[:title]).to eq "overridden title" 40 | end 41 | 42 | it "assigns a default value" do 43 | expect(subject[:body]).to eq "default body" 44 | end 45 | 46 | it "assigns a lazy, dependent attribute" do 47 | expect(subject[:summary]).to eq "overridden title" 48 | end 49 | 50 | it "doesn't assign associations" do 51 | expect(subject).not_to have_key(:user_id) 52 | expect(subject).not_to have_key(:user) 53 | end 54 | end 55 | 56 | describe "calling `attributes_for` with a block" do 57 | include FactoryBot::Syntax::Methods 58 | 59 | before do 60 | define_model("Company", name: :string) 61 | 62 | FactoryBot.define do 63 | factory :company 64 | end 65 | end 66 | 67 | it "passes the hash of attributes" do 68 | attributes_for(:company, name: "thoughtbot") do |attributes| 69 | expect(attributes[:name]).to eq("thoughtbot") 70 | end 71 | end 72 | 73 | it "returns the hash of attributes" do 74 | expected = nil 75 | 76 | result = attributes_for(:company) { |attributes| 77 | expected = attributes 78 | "hello!" 79 | } 80 | expect(result).to eq expected 81 | end 82 | end 83 | 84 | describe "`attributes_for` for a class whose constructor has required params" do 85 | before do 86 | define_model("User", name: :string) do 87 | def initialize(arg1, arg2) 88 | # Constructor requesting params to be used for testing 89 | end 90 | end 91 | 92 | FactoryBot.define do 93 | factory :user do 94 | name { "John Doe" } 95 | end 96 | end 97 | end 98 | 99 | subject { FactoryBot.attributes_for(:user) } 100 | its([:name]) { should eq "John Doe" } 101 | end 102 | -------------------------------------------------------------------------------- /spec/factory_bot/strategy/stub_spec.rb: -------------------------------------------------------------------------------- 1 | shared_examples "disabled persistence method" do |method_name| 2 | let(:instance) { described_class.new.result(evaluation) } 3 | 4 | describe "overriding persistence method: ##{method_name}" do 5 | it "overrides the method with any arity" do 6 | method = instance.method(method_name) 7 | 8 | expect(method.arity).to eq(-1) 9 | end 10 | 11 | it "raises an informative error if the method is called" do 12 | expect { instance.send(method_name) }.to raise_error( 13 | RuntimeError, 14 | "stubbed models are not allowed to access the database - #{instance.class}##{method_name}()" 15 | ) 16 | end 17 | end 18 | end 19 | 20 | describe FactoryBot::Strategy::Stub do 21 | it_should_behave_like "strategy with association support", :build_stubbed 22 | it_should_behave_like "strategy with callbacks", :after_stub 23 | it_should_behave_like "strategy with strategy: :build", :build_stubbed 24 | 25 | context "asking for a result" do 26 | let(:result_instance) do 27 | define_class("ResultInstance") { 28 | attr_accessor :id, :created_at 29 | }.new 30 | end 31 | 32 | let(:evaluation) do 33 | double("evaluation", object: result_instance, notify: true) 34 | end 35 | 36 | it { expect(subject.result(evaluation)).not_to be_new_record } 37 | it { expect(subject.result(evaluation)).to be_persisted } 38 | it { expect(subject.result(evaluation)).not_to be_destroyed } 39 | 40 | it "assigns created_at" do 41 | created_at1 = subject.result(evaluation).created_at 42 | created_at2 = subject.result(evaluation).created_at 43 | 44 | expect(created_at1).to equal created_at2 45 | end 46 | 47 | include_examples "disabled persistence method", :connection 48 | include_examples "disabled persistence method", :decrement! 49 | include_examples "disabled persistence method", :delete 50 | include_examples "disabled persistence method", :destroy 51 | include_examples "disabled persistence method", :destroy! 52 | include_examples "disabled persistence method", :increment! 53 | include_examples "disabled persistence method", :reload 54 | include_examples "disabled persistence method", :save 55 | include_examples "disabled persistence method", :save! 56 | include_examples "disabled persistence method", :toggle! 57 | include_examples "disabled persistence method", :touch 58 | include_examples "disabled persistence method", :update 59 | include_examples "disabled persistence method", :update! 60 | include_examples "disabled persistence method", :update_attribute 61 | include_examples "disabled persistence method", :update_attributes 62 | include_examples "disabled persistence method", :update_attributes! 63 | include_examples "disabled persistence method", :update_column 64 | include_examples "disabled persistence method", :update_columns 65 | end 66 | end 67 | --------------------------------------------------------------------------------