Back to Repositories

Testing SingleAttributeIterator Parameter Validation in grape

This test suite examines the SingleAttributeIterator class in Grape’s validation system, focusing on parameter iteration and validation handling. It verifies the iterator’s behavior across different parameter types and structures, ensuring robust parameter validation for both single values and arrays.

Test Coverage Overview

The test suite provides comprehensive coverage of the SingleAttributeIterator’s #each method functionality.

  • Tests parameter handling for both hash and array inputs
  • Verifies iteration over multiple attributes
  • Covers empty value detection and handling
  • Tests optional parameter processing

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with context-specific test cases.

Key implementation features include:
  • Double objects for validator simulation
  • Nested context blocks for different parameter scenarios
  • Yield expectation verification using block arguments
  • Structured test organization following the arrange-act-assert pattern

Technical Details

Testing infrastructure includes:

  • RSpec as the testing framework
  • Grape::API class initialization
  • ParamsScope implementation
  • Mock objects for validator interaction
  • Block argument testing with yield_successive_args matcher

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Clear context separation and naming
  • Comprehensive edge case coverage
  • DRY principle through shared setup
  • Explicit expectation definition
  • Focused test scenarios with single responsibility

ruby-grape/grape

spec/grape/validations/single_attribute_iterator_spec.rb

            
# frozen_string_literal: true

describe Grape::Validations::SingleAttributeIterator do
  describe '#each' do
    subject(:iterator) { described_class.new(validator, scope, params) }

    let(:scope) { Grape::Validations::ParamsScope.new(api: Class.new(Grape::API)) }
    let(:validator) { double(attrs: %i[first second]) }

    context 'when params is a hash' do
      let(:params) do
        { first: 'string', second: 'string' }
      end

      it 'yields params and every single attribute from the list' do
        expect { |b| iterator.each(&b) }
          .to yield_successive_args([params, :first, false], [params, :second, false])
      end
    end

    context 'when params is an array' do
      let(:params) do
        [{ first: 'string1', second: 'string1' }, { first: 'string2', second: 'string2' }]
      end

      it 'yields every single attribute from the list for each of the array elements' do
        expect { |b| iterator.each(&b) }.to yield_successive_args(
          [params[0], :first, false], [params[0], :second, false],
          [params[1], :first, false], [params[1], :second, false]
        )
      end

      context 'empty values' do
        let(:params) { [{}, '', 10] }

        it 'marks params with empty values' do
          expect { |b| iterator.each(&b) }.to yield_successive_args(
            [params[0], :first, true], [params[0], :second, true],
            [params[1], :first, true], [params[1], :second, true],
            [params[2], :first, false], [params[2], :second, false]
          )
        end
      end

      context 'when missing optional value' do
        let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue, 10] }

        it 'does not yield skipped values' do
          expect { |b| iterator.each(&b) }.to yield_successive_args(
            [params[1], :first, false], [params[1], :second, false]
          )
        end
      end
    end
  end
end