Back to Repositories

Testing Inheritable Configuration Management in ruby-grape/grape

This test suite validates the functionality of Grape’s InheritableSetting utility class, which manages configuration inheritance and scoping in the Grape framework. The tests ensure proper handling of global, namespace, and route-specific settings with various inheritance patterns.

Test Coverage Overview

The test suite provides comprehensive coverage of InheritableSetting’s core functionality:
  • Global setting management and inheritance
  • Namespace-scoped configuration handling
  • Route-specific setting isolation
  • Stackable and reverse stackable value management
  • Point-in-time copying and inheritance chain verification

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with extensive use of let blocks and nested describe contexts. The implementation validates setting inheritance, value isolation, and proper scoping through carefully structured test scenarios that verify the integrity of different setting types.

Technical Details

Testing infrastructure includes:
  • RSpec as the testing framework
  • Before hooks for test isolation
  • Custom setting objects for parent/child relationships
  • Mocking and expectation verification
  • Detailed hash structure validation

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Proper test isolation through before blocks
  • Comprehensive edge case coverage
  • Clear test organization by functionality
  • Thorough verification of object state
  • Effective use of RSpec’s expect syntax

ruby-grape/grape

spec/grape/util/inheritable_setting_spec.rb

            
# frozen_string_literal: true

describe Grape::Util::InheritableSetting do
  before do
    described_class.reset_global!
    subject.inherit_from parent
  end

  let(:parent) do
    described_class.new.tap do |settings|
      settings.global[:global_thing] = :global_foo_bar
      settings.namespace[:namespace_thing] = :namespace_foo_bar
      settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar
      settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar
      settings.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :namespace_reverse_stackable_foo_bar
      settings.route[:route_thing] = :route_foo_bar
    end
  end

  let(:other_parent) do
    described_class.new.tap do |settings|
      settings.namespace[:namespace_thing] = :namespace_foo_bar_other
      settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar_other
      settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar_other
      settings.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :namespace_reverse_stackable_foo_bar_other
      settings.route[:route_thing] = :route_foo_bar_other
    end
  end

  describe '#global' do
    it 'sets a global value' do
      subject.global[:some_thing] = :foo_bar
      expect(subject.global[:some_thing]).to eq :foo_bar
      subject.global[:some_thing] = :foo_bar_next
      expect(subject.global[:some_thing]).to eq :foo_bar_next
    end

    it 'sets the global inherited values' do
      expect(subject.global[:global_thing]).to eq :global_foo_bar
    end

    it 'overrides global values' do
      subject.global[:global_thing] = :global_new_foo_bar
      expect(parent.global[:global_thing]).to eq :global_new_foo_bar
    end

    it 'handles different parents' do
      subject.global[:global_thing] = :global_new_foo_bar

      subject.inherit_from other_parent

      expect(parent.global[:global_thing]).to eq :global_new_foo_bar
      expect(other_parent.global[:global_thing]).to eq :global_new_foo_bar
    end
  end

  describe '#api_class' do
    it 'is specific to the class' do
      subject.api_class[:some_thing] = :foo_bar
      parent.api_class[:some_thing] = :some_thing

      expect(subject.api_class[:some_thing]).to eq :foo_bar
      expect(parent.api_class[:some_thing]).to eq :some_thing
    end
  end

  describe '#namespace' do
    it 'sets a value until the end of a namespace' do
      subject.namespace[:some_thing] = :foo_bar
      expect(subject.namespace[:some_thing]).to eq :foo_bar
    end

    it 'uses new values when a new namespace starts' do
      subject.namespace[:namespace_thing] = :new_namespace_foo_bar
      expect(subject.namespace[:namespace_thing]).to eq :new_namespace_foo_bar

      expect(parent.namespace[:namespace_thing]).to eq :namespace_foo_bar
    end
  end

  describe '#namespace_inheritable' do
    it 'works with inheritable values' do
      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar
    end

    it 'handles different parents' do
      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar

      subject.inherit_from other_parent

      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar_other

      subject.inherit_from parent

      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar

      subject.inherit_from other_parent

      subject.namespace_inheritable[:namespace_inheritable_thing] = :my_thing

      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_thing

      subject.inherit_from parent

      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_thing
    end
  end

  describe '#namespace_stackable' do
    it 'works with stackable values' do
      expect(subject.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar]

      subject.inherit_from other_parent

      expect(subject.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar_other]
    end
  end

  describe '#namespace_reverse_stackable' do
    it 'works with reverse stackable values' do
      expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar]

      subject.inherit_from other_parent

      expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar_other]
    end
  end

  describe '#route' do
    it 'sets a value until the next route' do
      subject.route[:some_thing] = :foo_bar
      expect(subject.route[:some_thing]).to eq :foo_bar

      subject.route_end

      expect(subject.route[:some_thing]).to be_nil
    end

    it 'works with route values' do
      expect(subject.route[:route_thing]).to eq :route_foo_bar
    end
  end

  describe '#api_class' do
    it 'is specific to the class' do
      subject.api_class[:some_thing] = :foo_bar
      expect(subject.api_class[:some_thing]).to eq :foo_bar
    end
  end

  describe '#inherit_from' do
    it 'notifies clones' do
      new_settings = subject.point_in_time_copy
      expect(new_settings).to receive(:inherit_from).with(other_parent)

      subject.inherit_from other_parent
    end
  end

  describe '#point_in_time_copy' do
    let!(:cloned_obj) { subject.point_in_time_copy }

    it 'resets point_in_time_copies' do
      expect(cloned_obj.point_in_time_copies).to be_empty
    end

    it 'decouples namespace values' do
      subject.namespace[:namespace_thing] = :namespace_foo_bar

      cloned_obj.namespace[:namespace_thing] = :new_namespace_foo_bar
      expect(subject.namespace[:namespace_thing]).to eq :namespace_foo_bar
    end

    it 'decouples namespace inheritable values' do
      expect(cloned_obj.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar

      subject.namespace_inheritable[:namespace_inheritable_thing] = :my_thing
      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_thing

      expect(cloned_obj.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar

      cloned_obj.namespace_inheritable[:namespace_inheritable_thing] = :my_cloned_thing
      expect(cloned_obj.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_cloned_thing
      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_thing
    end

    it 'decouples namespace stackable values' do
      expect(cloned_obj.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar]

      subject.namespace_stackable[:namespace_stackable_thing] = :other_thing
      expect(subject.namespace_stackable[:namespace_stackable_thing]).to eq %i[namespace_stackable_foo_bar other_thing]
      expect(cloned_obj.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar]
    end

    it 'decouples namespace reverse stackable values' do
      expect(cloned_obj.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar]

      subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :other_thing
      expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq %i[other_thing namespace_reverse_stackable_foo_bar]
      expect(cloned_obj.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar]
    end

    it 'decouples route values' do
      expect(cloned_obj.route[:route_thing]).to eq :route_foo_bar

      subject.route[:route_thing] = :new_route_foo_bar
      expect(cloned_obj.route[:route_thing]).to eq :route_foo_bar
    end

    it 'adds itself to original as clone' do
      expect(subject.point_in_time_copies).to include(cloned_obj)
    end
  end

  describe '#to_hash' do
    it 'return all settings as a hash' do
      subject.global[:global_thing] = :global_foo_bar
      subject.namespace[:namespace_thing] = :namespace_foo_bar
      subject.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar
      subject.namespace_stackable[:namespace_stackable_thing] = [:namespace_stackable_foo_bar]
      subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = [:namespace_reverse_stackable_foo_bar]
      subject.route[:route_thing] = :route_foo_bar
      expect(subject.to_hash).to match(
        global: { global_thing: :global_foo_bar },
        namespace: { namespace_thing: :namespace_foo_bar },
        namespace_inheritable: {
          namespace_inheritable_thing: :namespace_inheritable_foo_bar
        },
        namespace_stackable: { namespace_stackable_thing: [:namespace_stackable_foo_bar, [:namespace_stackable_foo_bar]] },
        namespace_reverse_stackable:
          { namespace_reverse_stackable_thing: [[:namespace_reverse_stackable_foo_bar], :namespace_reverse_stackable_foo_bar] },
        route: { route_thing: :route_foo_bar }
      )
    end
  end
end