Back to Repositories

Testing Factory List Generation and Transient Attributes in factory_bot

This test suite examines the create_list functionality in FactoryBot, focusing on generating multiple test instances efficiently. It validates both basic instance creation and complex scenarios involving transient attributes and associations in a Ruby testing environment.

Test Coverage Overview

The test suite comprehensively covers multiple aspects of FactoryBot’s create_list functionality:

  • Basic multiple instance creation with default attributes
  • Override capabilities for default attributes
  • Block-based instance customization
  • Index-aware instance creation
  • Error handling for invalid parameters
  • Complex scenarios with transient attributes and associations

Implementation Analysis

The testing approach employs RSpec’s describe/context structure to organize different test scenarios systematically. It leverages FactoryBot’s factory definitions and dynamic attribute generation, demonstrating both simple and complex factory patterns with associations and callbacks.

The implementation showcases factory inheritance, transient attributes, and after-create callbacks for establishing relationships between models.

Technical Details

  • Testing Framework: RSpec
  • Factory Management: FactoryBot
  • Model Definition: Dynamic model creation with attributes
  • Association Testing: has_many/belongs_to relationships
  • Custom Helpers: define_model for test setup

Best Practices Demonstrated

The test suite exemplifies several testing best practices including isolated test contexts, comprehensive edge case coverage, and clear test organization. It demonstrates proper setup isolation using before blocks, consistent expectation patterns, and effective use of subject blocks for DRY test cases.

The implementation shows proper error handling testing and efficient test data generation using factory patterns.

thoughtbot/factory_bot

spec/acceptance/create_list_spec.rb

            
describe "create multiple instances" do
  before do
    define_model("Post", title: :string, position: :integer)

    FactoryBot.define do
      factory(:post) do |post|
        post.title { "Through the Looking Glass" }
        post.position { rand(10**4) }
      end
    end
  end

  context "without default attributes" do
    subject { FactoryBot.create_list(:post, 20) }

    its(:length) { should eq 20 }

    it "creates all the posts" do
      subject.each do |record|
        expect(record).not_to be_new_record
      end
    end

    it "uses the default factory values" do
      subject.each do |record|
        expect(record.title).to eq "Through the Looking Glass"
      end
    end
  end

  context "with default attributes" do
    subject { FactoryBot.create_list(:post, 20, title: "The Hunting of the Snark") }

    it "overrides the default values" do
      subject.each do |record|
        expect(record.title).to eq "The Hunting of the Snark"
      end
    end
  end

  context "with a block" do
    subject do
      FactoryBot.create_list(:post, 20, title: "The Listing of the Block") do |post|
        post.position = post.id
      end
    end

    it "uses the new values" do
      subject.each do |record|
        expect(record.position).to eq record.id
      end
    end
  end

  context "with a block that receives both the object and an index" do
    subject do
      FactoryBot.create_list(:post, 20, title: "The Indexed Block") do |post, index|
        post.position = index
      end
    end

    it "uses the new values" do
      subject.each_with_index do |record, index|
        expect(record.position).to eq index
      end
    end
  end

  context "without the count" do
    subject { FactoryBot.create_list(:post, title: "The Hunting of the Bear") }

    it "raise ArgumentError with the proper error message" do
      expect { subject }.to raise_error(ArgumentError, /count missing/)
    end
  end
end

describe "multiple creates and transient attributes to dynamically build attribute lists" do
  before do
    define_model("User", name: :string) do
      has_many :posts
    end

    define_model("Post", title: :string, user_id: :integer) do
      belongs_to :user
    end

    FactoryBot.define do
      factory :post do
        title { "Through the Looking Glass" }
        user
      end

      factory :user do
        name { "John Doe" }

        factory :user_with_posts do
          transient do
            posts_count { 5 }
          end

          after(:create) do |user, evaluator|
            FactoryBot.create_list(:post, evaluator.posts_count, user: user)
          end
        end
      end
    end
  end

  it "generates the correct number of posts" do
    expect(FactoryBot.create(:user_with_posts).posts.length).to eq 5
  end

  it "allows the number of posts to be modified" do
    expect(FactoryBot.create(:user_with_posts, posts_count: 2).posts.length).to eq 2
  end
end