Back to Repositories

Testing Dynamic Attribute Generation in factory_bot

This test suite examines the Dynamic attribute functionality in FactoryBot, focusing on dynamic value generation and attribute handling. It verifies various scenarios including static values, block-level variables, and sequence implementations to ensure robust attribute generation in factory definitions.

Test Coverage Overview

The test suite provides comprehensive coverage of FactoryBot’s Dynamic attribute behavior across multiple scenarios.

Key areas tested include:
  • Static value generation through blocks
  • Block-level variable handling
  • Attribute reference resolution
  • Sequence integration and abuse prevention
  • String name normalization to symbols

Implementation Analysis

The testing approach utilizes RSpec’s describe/context pattern to organize different Dynamic attribute scenarios. Implementation details focus on proc execution, method missing behavior, and sequence handling.

Technical patterns include:
  • Lambda block implementations
  • Dynamic method extension
  • Mock object configuration
  • Error condition validation

Technical Details

Testing infrastructure includes:
  • RSpec as the testing framework
  • Subject/let syntax for test setup
  • Mock object extension via anonymous modules
  • Dynamic attribute instantiation
  • Sequence registration testing

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through organized test structure and comprehensive coverage.

Notable practices include:
  • Isolated test contexts
  • Clear scenario separation
  • Edge case handling
  • Mock object proper usage
  • Explicit error condition testing

thoughtbot/factory_bot

spec/factory_bot/attribute/dynamic_spec.rb

            
describe FactoryBot::Attribute::Dynamic do
  let(:name) { :first_name }
  let(:block) { -> {} }

  subject { FactoryBot::Attribute::Dynamic.new(name, false, block) }

  its(:name) { should eq name }

  context "with a block returning a static value" do
    let(:block) { -> { "value" } }

    it "returns the value when executing the proc" do
      expect(subject.to_proc.call).to eq "value"
    end
  end

  context "with a block returning its block-level variable" do
    let(:block) { ->(thing) { thing } }

    it "returns self when executing the proc" do
      expect(subject.to_proc.call).to eq subject
    end
  end

  context "with a block referencing an attribute on the attribute" do
    let(:block) { -> { attribute_defined_on_attribute } }
    let(:result) { "other attribute value" }

    before do
      # Define an '#attribute_defined_on_attribute' instance method allowing it
      # be mocked. Usually this is determined via '#method_missing'
      missing_methods = Module.new {
        def attribute_defined_on_attribute(*args)
        end
      }
      subject.extend(missing_methods)

      allow(subject)
        .to receive(:attribute_defined_on_attribute).and_return result
    end

    it "evaluates the attribute from the attribute" do
      expect(subject.to_proc.call).to eq result
    end
  end

  context "with a block returning a sequence" do
    let(:block) do
      -> do
        FactoryBot::Internal.register_sequence(
          FactoryBot::Sequence.new(:email, 1) { |n| "foo#{n}" }
        )
      end
    end

    it "raises a sequence abuse error" do
      expect { subject.to_proc.call }.to raise_error(FactoryBot::SequenceAbuseError)
    end
  end
end

describe FactoryBot::Attribute::Dynamic, "with a string name" do
  subject { FactoryBot::Attribute::Dynamic.new("name", false, -> {}) }
  its(:name) { should eq :name }
end