Back to Repositories

Testing Enum Trait Generation and Handling in Factory Bot

This test suite validates the enum traits functionality in Factory Bot, focusing on automatic trait generation for enum fields in ActiveRecord models. It covers both automatic and manual trait definition scenarios, ensuring proper integration with different Ruby on Rails versions.

Test Coverage Overview

The test suite provides comprehensive coverage of enum trait behavior in Factory Bot.

Key areas tested include:
  • Automatic trait generation for enum fields
  • Custom trait definition precedence
  • Support for different enum value formats (Hash, Array, Custom Enumerable)
  • Behavior with automatically_define_enum_traits enabled and disabled

Implementation Analysis

The testing approach employs RSpec to verify Factory Bot’s enum trait functionality across different scenarios.

Key implementation patterns include:
  • Dynamic model definition with configurable enum fields
  • Version-specific ActiveRecord enum syntax handling
  • Temporary configuration toggles for feature testing
  • Iterative test cases across enum values

Technical Details

Testing infrastructure includes:
  • RSpec as the testing framework
  • Factory Bot for test data generation
  • Dynamic class definition helpers
  • ActiveRecord integration testing
  • Custom helper methods for model and enum definition

Best Practices Demonstrated

The test suite exemplifies several testing best practices.

Notable examples include:
  • Isolated test contexts for different configuration states
  • Comprehensive edge case coverage
  • Clear test case organization
  • DRY helper methods for common setup tasks
  • Explicit expectations and error case validation

thoughtbot/factory_bot

spec/acceptance/enum_traits_spec.rb

            
describe "enum traits" do
  def define_model_with_enum(class_name, field, values)
    define_model(class_name, status: :integer) do
      if ActiveRecord::VERSION::STRING >= "7.0"
        enum field, values
      else
        enum field => values
      end
    end
  end

  context "when automatically_define_enum_traits is true" do
    it "builds traits automatically for model enum field" do
      define_model_with_enum("Task", :status, {queued: 0, started: 1, finished: 2})

      FactoryBot.define do
        factory :task
      end

      Task.statuses.each_key do |trait_name|
        task = FactoryBot.build(:task, trait_name)

        expect(task.status).to eq(trait_name)
      end

      Task.reset_column_information
    end

    it "prefers user defined traits over automatically built traits" do
      define_model_with_enum("Task", :status, {queued: 0, started: 1, finished: 2})

      FactoryBot.define do
        factory :task do
          trait :queued do
            status { :finished }
          end

          trait :started do
            status { :finished }
          end

          trait :finished do
            status { :finished }
          end
        end
      end

      Task.statuses.each_key do |trait_name|
        task = FactoryBot.build(:task, trait_name)

        expect(task.status).to eq("finished")
      end

      Task.reset_column_information
    end

    it "builds traits for each enumerated value using a provided list of values as a Hash" do
      statuses = {queued: 0, started: 1, finished: 2}

      define_class "Task" do
        attr_accessor :status
      end

      FactoryBot.define do
        factory :task do
          traits_for_enum :status, statuses
        end
      end

      statuses.each do |trait_name, trait_value|
        task = FactoryBot.build(:task, trait_name)

        expect(task.status).to eq(trait_value)
      end
    end

    it "builds traits for each enumerated value using a provided list of values as an Array" do
      statuses = %w[queued started finished]

      define_class "Task" do
        attr_accessor :status
      end

      FactoryBot.define do
        factory :task do
          traits_for_enum :status, statuses
        end
      end

      statuses.each do |trait_name|
        task = FactoryBot.build(:task, trait_name)

        expect(task.status).to eq(trait_name)
      end
    end

    it "builds traits for each enumerated value using a custom enumerable" do
      statuses = define_class("Statuses") {
        include Enumerable

        def each(&block)
          ["queued", "started", "finished"].each(&block)
        end
      }.new

      define_class "Task" do
        attr_accessor :status
      end

      FactoryBot.define do
        factory :task do
          traits_for_enum :status, statuses
        end
      end

      statuses.each do |trait_name|
        task = FactoryBot.build(:task, trait_name)

        expect(task.status).to eq(trait_name)
      end
    end
  end

  context "when automatically_define_enum_traits is false" do
    it "raises an error for undefined traits" do
      with_temporary_assignment(FactoryBot, :automatically_define_enum_traits, false) do
        define_model_with_enum("Task", :status, {queued: 0, started: 1, finished: 2})

        FactoryBot.define do
          factory :task
        end

        Task.statuses.each_key do |trait_name|
          expect { FactoryBot.build(:task, trait_name) }.to raise_error(
            KeyError, "Trait not registered: \"#{trait_name}\""
          )
        end

        Task.reset_column_information
      end
    end

    it "builds traits for each enumerated value when traits_for_enum are specified" do
      with_temporary_assignment(FactoryBot, :automatically_define_enum_traits, false) do
        define_model_with_enum("Task", :status, {queued: 0, started: 1, finished: 2})

        FactoryBot.define do
          factory :task do
            traits_for_enum(:status)
          end
        end

        Task.statuses.each_key do |trait_name|
          task = FactoryBot.build(:task, trait_name)

          expect(task.status).to eq(trait_name)
        end

        Task.reset_column_information
      end
    end
  end
end