Back to Repositories

Testing Sequence Generation Implementation in factory_bot

This test suite validates the sequence functionality in FactoryBot, a Ruby test data generation framework. It thoroughly examines sequence behavior including basic and custom sequences, aliases, and scoped increments.

Test Coverage Overview

The test suite provides comprehensive coverage of FactoryBot’s sequence implementation.

Key areas tested include:
  • Basic sequence generation with default and custom values
  • Sequence behavior with aliases
  • Enumerable sequence handling
  • Scoped sequence operations
  • Sequence rewinding functionality

Implementation Analysis

The testing approach utilizes RSpec’s shared examples pattern to maintain DRY principles across multiple sequence variants. The implementation leverages RSpec’s context-specific features including let declarations, subject blocks, and custom matchers for behavior verification.

Technical patterns include:
  • Shared behavior testing through shared_examples
  • Dynamic value generation with blocks
  • Scope isolation testing
  • Exception handling verification

Technical Details

Testing tools and configuration:
  • RSpec as the testing framework
  • Custom sequence implementations
  • Mock objects for scope testing
  • Enumerator handling
  • Block-based value generation

Best Practices Demonstrated

The test suite exemplifies several testing best practices in Ruby and RSpec.

Notable practices include:
  • DRY principle application through shared examples
  • Comprehensive edge case coverage
  • Clear test organization and naming
  • Proper isolation of test scenarios
  • Effective use of RSpec’s DSL features

thoughtbot/factory_bot

spec/factory_bot/sequence_spec.rb

            
shared_examples "a sequence" do |options|
  first_value = options[:first_value]
  second_value = options[:second_value]

  it "has a next value equal to its first value" do
    expect(subject.next).to eq first_value
  end

  it "has a next value equal to the 2nd value after being incremented" do
    subject.next

    expect(subject.next).to eq second_value
  end

  it "has a next value equal to the 1st value after rewinding" do
    subject.next
    subject.rewind

    expect(subject.next).to eq first_value
  end
end

describe FactoryBot::Sequence do
  describe "a basic sequence" do
    let(:name) { :test }
    subject { FactoryBot::Sequence.new(name) { |n| "=#{n}" } }

    its(:name) { should eq name }
    its(:names) { should eq [name] }

    it_behaves_like "a sequence", first_value: "=1", second_value: "=2"
  end

  describe "a custom sequence" do
    subject { FactoryBot::Sequence.new(:name, "A") { |n| "=#{n}" } }

    it_behaves_like "a sequence", first_value: "=A", second_value: "=B"
  end

  describe "a sequence with aliases using default value" do
    subject do
      FactoryBot::Sequence.new(:test, aliases: [:alias, :other]) do |n|
        "=#{n}"
      end
    end

    it "has the expected names as its names" do
      names = [:foo, :bar, :baz]
      sequence = FactoryBot::Sequence.new(names.first, aliases: names.last(2)) {
        "=#{n}"
      }

      expect(sequence.names).to eq names
    end

    it_behaves_like "a sequence", first_value: "=1", second_value: "=2"
  end

  describe "a sequence with custom value and aliases" do
    subject do
      FactoryBot::Sequence.new(:test, 3, aliases: [:alias, :other]) do |n|
        "=#{n}"
      end
    end

    it "has the expected names as its names" do
      names = [:foo, :bar, :baz]
      sequence = FactoryBot::Sequence.new(names.first, 3, aliases: names.last(2)) {
        "=#{n}"
      }
      expect(sequence.names).to eq names
    end

    it_behaves_like "a sequence", first_value: "=3", second_value: "=4"
  end

  describe "a basic sequence without a block" do
    subject { FactoryBot::Sequence.new(:name) }

    it_behaves_like "a sequence", first_value: 1, second_value: 2
  end

  describe "a custom sequence without a block" do
    subject { FactoryBot::Sequence.new(:name, "A") }

    it_behaves_like "a sequence", first_value: "A", second_value: "B"
  end

  describe "iterating over items in an enumerator" do
    subject do
      FactoryBot::Sequence.new(:name, %w[foo bar].to_enum) { |n| "=#{n}" }
    end

    it "navigates to the next items until no items remain" do
      sequence = FactoryBot::Sequence.new(:name, %w[foo bar].to_enum) { |n| "=#{n}" }
      expect(sequence.next).to eq "=foo"
      expect(sequence.next).to eq "=bar"

      expect { sequence.next }.to raise_error(StopIteration)
    end

    it_behaves_like "a sequence", first_value: "=foo", second_value: "=bar"
  end

  it "a custom sequence and scope increments within the correct scope" do
    sequence = FactoryBot::Sequence.new(:name, "A") { |n| "=#{n}#{foo}" }
    scope = double("scope", foo: "attribute")

    expect(sequence.next(scope)).to eq "=Aattribute"
  end

  it "a custom sequence and scope increments within the correct scope when incrementing" do
    sequence = FactoryBot::Sequence.new(:name, "A") { |n| "=#{n}#{foo}" }
    scope = double("scope", foo: "attribute")
    sequence.next(scope)

    expect(sequence.next(scope)).to eq "=Battribute"
  end

  it "a custom scope increments within the correct scope after rewinding" do
    sequence = FactoryBot::Sequence.new(:name, "A") { |n| "=#{n}#{foo}" }
    scope = double("scope", foo: "attribute")
    sequence.next(scope)
    sequence.rewind

    expect(sequence.next(scope)).to eq "=Aattribute"
  end
end