Module: RSpec::SleepingKingStudios::Contract

Defined in:
lib/rspec/sleeping_king_studios/contract.rb

Overview

A Contract wraps RSpec functionality for sharing and reusability.

An RSpec::SleepingKingStudios::Contract integrates with the .include_contract class method to share and reuse RSpec examples and configuration. The major advantage a Contract object provides over using a Proc is documentation - tools such as YARD do not gracefully handle bare lambdas, while the functionality and requirements of a Contract can be specified using standard patterns, such as documenting the parameters passed to a contract using the #apply method.

Examples:

Defining A Contract

module ExampleContracts
  # This contract asserts that the object has the Enumerable module as an
  # ancestor, and that it responds to the #each method.
  class ShouldBeEnumerableContract
    extend RSpec::SleepingKingStudios::Contract

    # @!method apply(example_group)
    #   Adds the contract to the example group.

    contract do
      it 'should be Enumerable' do
        expect(subject).to be_a Enumerable
      end

      it 'should respond to #each' do
        expect(subject).to respond_to(:each).with(0).arguments
      end
    end
  end
end

RSpec.describe Array do
  ExampleContracts::SHOULD_BE_ENUMERABLE_CONTRACT.apply(self)
end

RSpec.describe Hash do
  extend  RSpec::SleepingKingStudios::Concerns::IncludeContract
  include ExampleContracts

  include_contract 'should be enumerable'
end

Defining A Contract With Parameters

module SerializerContracts
  # This contract asserts that the serialized result has the expected
  # values.
  #
  # First, we pass the contract a series of attribute names. These are
  # used to assert that the serialized attributes match the values on the
  # original object.
  #
  # Second, we pass the contract a set of attribute names and values.
  # These are used to assert that the serialized attributes have the
  # specified values.
  #
  # Finally, we can pass the contract a block, which the contract then
  # executes. Note that the block is executed in the context of our
  # describe block, and thus can take advantage of our memoized
  # #serialized helper method.
  class ShouldSerializeAttributesContract
    extend RSpec::SleepingKingStudios::Contract

    contract do |*attributes, **values, &block|
      describe '#serialize' do
        let(:serialized) { subject.serialize }

        it { expect(subject).to respond_to(:serialize).with(0).arguments }

        attributes.each do |attribute|
          it "should serialize #{attribute}" do
            expect(serialized[attribute]).to be == subject[attribute]
          end
        end

        values.each do |attribute, value|
          it "should serialize #{attribute}" do
            expect(serialized[attribute]).to be == value
          end
        end

        instance_exec(&block) if block
      end
    end
  end

RSpec.describe CaptainPicard do
  SerializerContracts::ShouldSerializeAttributesContract.apply(
    self,
    :name,
    :rank,
    lights: 4) \
  do
    it 'should serialize the catchphrase' do
      expect(serialized[:catchphrase]).to be == 'Make it so.'
    end
  end
end

See Also:

Instance Method Summary collapse

Instance Method Details

#apply(example_group, *arguments, **keywords) { ... } ⇒ Object

Adds the contract to the given example group.

Parameters:

  • example_group (RSpec::Core::ExampleGroup)

    The example group to which the contract is applied.

  • arguments (Array)

    Optional arguments to pass to the contract.

  • keywords (Hash)

    Optional keywords to pass to the contract.

Yields:

  • A block to pass to the contract.

See Also:



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/rspec/sleeping_king_studios/contract.rb', line 118

def apply(example_group, *arguments, **keywords, &block)
  concern = RSpec::SleepingKingStudios::Concerns::IncludeContract

  concern.define_contract_method(
    context:  example_group,
    contract: self,
    name:     tools.str.underscore(name).gsub('::', '_')
  ) do |method_name|
    if keywords.empty?
      example_group.send(method_name, *arguments, &block)
    else
      example_group.send(method_name, *arguments, **keywords, &block)
    end
  end
end

#contractProc? #contract {|*arguments, **keywords, &block| ... } ⇒ Object

Overloads:

  • #contractProc?

    Returns the contract implementation for the class.

    Returns:

    • (Proc, nil)

      the contract implementation for the class.

  • #contract {|*arguments, **keywords, &block| ... } ⇒ Object

    Sets the contract implementation for the class.

    Yields:

    • (*arguments, **keywords, &block)

      The implementation to configure for the class.

    Yield Parameters:

    • arguments (Array)

      Optional arguments to pass to the contract.

    • keywords (Hash)

      Optional keywords defined for the contract.

    • block (Array)

      A block to pass to the contract.



148
149
150
151
152
# File 'lib/rspec/sleeping_king_studios/contract.rb', line 148

def contract(&block)
  return @contract = block if block_given?

  @contract
end

#to_procProc?

Returns the contract implementation for the class.

Returns:

  • (Proc, nil)

    the contract implementation for the class.



155
156
157
# File 'lib/rspec/sleeping_king_studios/contract.rb', line 155

def to_proc
  @contract
end