FixtureBot

repository·main·Indexed 41 days ago

https://github.com/rubymonolith/fixturebot

A Ruby DSL for defining test data that compiles into standard Rails YAML fixtures. It provides the syntactic sugar of factories with the performance and stability of Rails fixtures, featuring a mirroring API for FactoryBot and support for RSpec, Minitest, and non-Rails environments.

Tokens
4.7K
Snippets
21
Records
25
Agent score
52%

What's inside FixtureBot

  1. How FixtureBot compares to Rails fixtures, FactoryBot, and Oaken

    main

    FixtureBot provides a Ruby DSL that compiles down to standard YAML fixtures. This allows you to keep the speed of Rails fixtures while avoiding the maintenance pain of hand-written YAML.

    Key Comparisons

    FeatureFixtureBotRails fixturesFactoryBotOaken
    Define data inRuby DSLYAMLRuby DSLRuby scripts
    OutputYAML files in gitYAML files in gitDatabase rows per testDatabase rows at boot
    Runtime dependencyNoneNoneRequired per testRequired at boot
    Data setOne set, loaded onceOne set, loaded onceBuilt per testPer-test via seed files
    SpeedFast (fixtures)Fast (fixtures)Slow (inserts per test)Varies
    Stable IDsDeterministicDeterministicDatabase-assignedDatabase-assigned

    Core Philosophy

    • Vs Rails Fixtures: FixtureBot solves the
  2. Handle associations: belongs_to, polymorphic, and join tables

    main

    FixtureBot simplifies defining relationships between records.

    belongs_to

    Reference other records by their name to set the foreign key using their stable ID:

    post :hello_world do
      author :brad # sets author_id to brad's ID
    end

    Polymorphic associations

    Use the table helper to set both the _id and _type columns:

    # If 'votable' is a polymorphic association
    vote :upvote_post do
      votable post(:hello) # sets votable_id and votable_type: "Post"
    end

    Join tables (HABTM)

    Pass multiple record names to create rows in the join table:

    post :hello_world do
      tags :ruby, :rails # creates rows in posts_tags
    end
  3. Install FixtureBot in a Rails application

    main

    To use FixtureBot, add the fixturebot-rails gem to your Gemfile. You can then use the Rails generator to automate the setup of your fixture definition files and test helper integration.

    # Add to Gemfile
    gem "fixturebot-rails"
    # Run the generator
    rails generate fixturebot:install
  4. Configure FixtureBot manually for RSpec or Minitest

    main

    If you prefer not to use the generator, you can set up FixtureBot manually by requiring the appropriate integration in your test helper and creating a fixtures.rb file.

    RSpec

    1. Add require "fixturebot/rspec" to spec/rails_helper.rb.
    2. Create spec/fixtures.rb for your definitions.

    Minitest

    1. Add require "fixturebot/minitest" to test/test_helper.rb.
    2. Create test/fixtures.rb for your definitions.

    Fixtures are automatically generated before the test suite runs.

  5. Migrate from FactoryBot to FixtureBot

    main

    FixtureBot provides a DSL that mirrors FactoryBot's API, making it easy to transition. The primary difference is that FixtureBot methods require both a table name and a fixture name (or names) instead of just a factory name.

    Comparison Table

    FactoryBotFixtureBot
    build(:user)build(:user, :brad)
    create(:user)create(:user, :brad)
    build(:user, name: "X")build(:user, :brad, name: "X")
    attributes_for(:user)attributes_for(:user, :brad)
    build_list(:user, 3)build_list(:user, :brad, :alice, :bob)
    create_list(:user, 3)create_list(:user, :brad, :alice, :bob)
    build_pair(:user)build_pair(:user, :brad, :alice)
    create_pair(:user)create_pair(:user, :brad, :alice)
    build_stubbed(:user)build_stubbed(:user, :brad)
    # FactoryBot                    # FixtureBot
    build(:user)                    # build(:user, :brad)
    create(:user)                   # create(:user, :brad)
    build(:user, name: "X")        # build(:user, :brad, name: "X")
    attributes_for(:user)           # attributes_for(:user, :brad)
    build_list(:user, 3)            # build_list(:user, :brad, :alice, :bob)
    create_list(:user, 3)           # create_list(:user, :brad, :alice, :bob)
    build_pair(:user)               # build_pair(:user, :brad, :alice)
    create_pair(:user)              # create_pair(:user, :brad, :alice)
    build_stubbed(:user)            # build_stubbed(:user, :brad)
  6. Configure FixtureBot file paths and output directory

    main

    FixtureBot automatically detects your fixtures file (e.g., spec/fixtures.rb) and sets the output directory by stripping the .rb extension. You can override these settings in config/application.rb or config/environments/test.rb using the following configuration keys:

    config.fixturebot.fixtures_file = "test/my_fixtures.rb"
    config.fixturebot.output_dir = "test/fixtures"
  7. Install FixtureBot using the Rails generator

    main

    To set up FixtureBot in your Rails application, run the Rails generator. The generator automatically detects whether you are using RSpec or Minitest and performs the following actions:

    1. Creates a fixtures configuration file:
      • For RSpec: spec/fixtures.rb
      • For Minitest: test/fixtures.rb
    2. Injects the required FixtureBot integration into your test helper:
      • For RSpec: Adds require "fixturebot/rspec" to spec/rails_helper.rb after require 'rspec/rails'.
      • For Minitest: Adds require "fixturebot/minitest" to test/test_helper.rb after require "rails/test_help".
    rails generate fixturebot:install
  8. Define records and generators using the FixtureBot DSL

    main

    FixtureBot uses a Ruby DSL to define test data. You can define records with specific values or use generators to provide default values for columns across all records.

    Records

    Define a named record with a block to specify column values:

    user :brad do
      name "Brad"
      email "brad@example.com"
    end

    Generators

    Generators set default values for columns. They run for any record that doesn't explicitly set that column. The generator block receives a fixture object with a key method (the record's symbol name).

    # Sets email to "alice@example.com" for any user named :alice
    user.email { |fixture| "#{fixture.key}@example.com" }

    Overriding Generators

    Literal values or explicit nil values in a record block will shadow the generator.

    FixtureBot.define do
      user.email { |fixture| "#{fixture.key}@example.com" }
    
      user :brad do
        name "Brad"
        email "brad@example.com" # overrides generator
      end
    
      user :alice do
        name "Alice"             # uses generator: "alice@example.com"
      end
    
      user :deactivated do
        name "Ghost"
        email nil                  # explicit nil, skips generator
      end
    end
  9. Define manual schemas for non-Rails environments

    main

    If you are not using Rails, you must define your database schema manually using FixtureBot::Schema.define. This allows FixtureBot to understand tables, columns, and associations.

    FixtureBot::Schema.define do
      table :users, singular: :user, columns: [:name, :email]
    
      table :posts, singular: :post, columns: [:title, :body, :author_id] do
        belongs_to :author, table: :users
      end
    
      join_table :posts_tags, :posts, :tags
    end
  10. Configure primary keys and UUID support

    main

    FixtureBot generates deterministic, stable IDs. You can customize how these IDs are generated.

    Hardcoded IDs

    Override the deterministic ID with an explicit value:

    user :admin do
      id 42
    end

    UUIDs

    In Rails, UUID primary keys are auto-detected. For manual schema definitions, use FixtureBot::Key::Uuid to ensure deterministic UUID v5 generation.

    Custom Key Strategies

    You can provide a custom object that responds to generate(table_name, record_name) to implement custom ID logic (e.g., prefixed IDs).

    # Example of a custom key strategy
    module PrefixedKey
      module_function
      def generate(table_name, record_name)
        hash = Zlib.crc32("#{table_name}:#{record_name}").to_s(36)
        "#{table_name.to_s.chomp('s')}_#{hash}"
      end
    end
    
    FixtureBot::Schema.define do
      table :customers, singular: :customer, columns: [:name], key: PrefixedKey
    end
  11. Configure FixtureBot in Rails

    main

    You can configure the default paths for fixture compilation within your Rails application configuration. This allows you to avoid passing explicit paths to the compile method.

    Set the following keys under the fixturebot configuration object in your Rails environment files:

    • fixtures_file: The path to your fixture definition file (e.g., spec/fixtures.rb).
    • output_dir: The directory where compiled fixtures should be written.
    # Example configuration in config/environments/test.rb
    Rails.application.configure do
      config.fixturebot = {
        fixtures_file: 'spec/fixtures.rb',
        output_dir: 'spec/fixtures/compiled'
      }
    end
  12. FixtureBot Method Reference

    main

    These methods are automatically available in your tests when you require fixturebot/rspec or fixturebot/minitest. They act as wrappers around standard Rails fixture accessors (e.g., build(:user, :brad) is equivalent to users(:brad).dup).

    MethodBehavior
    build(:user, :brad, **attrs)Duplicates the fixture, applies overrides. Returns unpersisted.
    create(:user, :brad, **attrs)Without overrides: returns the fixture (already persisted). With overrides: build + save!.
    build_stubbed(:user, :brad, **attrs)Like build but retains id. Looks persisted without touching DB.
    attributes_for(:user, :brad, **attrs)Returns attributes hash, strips id/created_at/updated_at.
    build_list(:user, :brad, :alice, **attrs)Maps each name through build.
    create_list(:user, :brad, :alice, **attrs)Maps each name through create.
    build_pair(:user, :brad, :alice, **attrs)Alias for build_list with 2 names.
    create_pair(:user, :brad, :alice, **attrs)Alias for create_list with 2 names.
    build_stubbed_list(:user, :brad, :alice, **attrs)Maps each name through build_stubbed.
    build_stubbed_pair(:user, :brad, :alice, **attrs)Alias for build_stubbed_list with 2 names.