123456789_123456789_123456789_123456789_123456789_

Module: RSpec::Matchers::DSL::Macros

Relationships & Source Files
Namespace Children
Modules:
Extension / Inclusion / Inheritance Descendants
Extended In:
Defined in: rspec-expectations/lib/rspec/matchers/dsl.rb

Overview

Contains the methods that are available from within the ‘RSpec::Matchers.define` ::RSpec::Matchers::DSL for creating custom matchers.

Constant Summary

Instance Method Summary

Instance Method Details

#assign_attributes(attr_names) (private)

[ GitHub ]

  
# File 'rspec-expectations/lib/rspec/matchers/dsl.rb', line 312

def assign_attributes(attr_names)
  attr_reader(*attr_names)
  private(*attr_names)

  lambda do |*attr_values|
    attr_names.zip(attr_values) do |attr_name, attr_value|
      instance_variable_set(:"@#{attr_name}", attr_value)
    end
  end
end

#chain(method_name, *attr_names, &definition)

Convenience for defining methods on this matcher to create a fluent interface. The trick about fluent interfaces is that each method must return self in order to chain methods together. ‘chain` handles that for you. If the method is invoked and the include_chain_clauses_in_custom_matcher_descriptions config option hash been enabled, the chained method name and args will be added to the default description and failure message.

In the common case where you just want the chained method to store some value(s) for later use (e.g. in ‘match`), you can provide one or more attribute names instead of a block; the chained method will store its arguments in instance variables with those names, and the values will be exposed via getters.

Examples:

RSpec::Matchers.define :have_errors_on do |key|
  chain :with do |message|
    @message = message
  end

  match do |actual|
    actual.errors[key] == @message
  end
end

expect(minor).to have_errors_on(:age).with("Not old enough to participate")
[ GitHub ]

  
# File 'rspec-expectations/lib/rspec/matchers/dsl.rb', line 298

def chain(method_name, *attr_names, &definition)
  unless block_given? ^ attr_names.any?
    raise ArgumentError, "You must pass either a block or some attribute names (but not both) to `chain`."
  end

  definition = assign_attributes(attr_names) if attr_names.any?

  define_user_override(method_name, definition) do |*args, &block|
    super(*args, &block)
    @chained_method_clauses.push([method_name, args])
    self
  end
end

#define_user_override(method_name, user_def, &our_def) (private)

Does the following:

  • Defines the named method using a user-provided block in @user_method_defs, which is included as an ancestor in the singleton class in which we eval the ‘define` block.

  • Defines an overridden definition for the same method usign the provided ‘our_def` block.

  • Provides a default ‘our_def` block for the common case of needing to call the user’s definition with ‘@actual` as an arg, but only if their block’s arity can handle it.

This compiles the user block into an actual method, allowing them to use normal method constructs like ‘return` (e.g. for an early guard statement), while allowing us to define an override that can provide the wrapped handling (e.g. assigning @actual, rescueing errors, etc) and can super to the user’s definition.

[ GitHub ]

  
# File 'rspec-expectations/lib/rspec/matchers/dsl.rb', line 346

def define_user_override(method_name, user_def, &our_def)
  @user_method_defs.__send__(:define_method, method_name, &user_def)
  our_def ||= lambda { super(*actual_arg_for(user_def)) }
  define_method(method_name, &our_def)
end

#description {|Object| ... }

Customize the description to use for one-liners. Only use this when the description generated by default doesn’t suit your needs.

Examples:

RSpec::Matchers.define :qualify_for do |expected|
  match { your_match_logic }

  description do
    "qualify for #{expected}"
  end
end

Yields:

  • (Object)

    actual the actual object (i.e. the value wrapped by ‘expect`)

[ GitHub ]

  
# File 'rspec-expectations/lib/rspec/matchers/dsl.rb', line 253

def description(&definition)
  define_user_override(__method__, definition)
end

#diffable

Tells the matcher to diff the actual and expected values in the failure message.

[ GitHub ]

  
# File 'rspec-expectations/lib/rspec/matchers/dsl.rb', line 259

def diffable
  define_method(:diffable?) { true }
end

#failure_message {|Object| ... }

Customizes the failure message to use when this matcher is asked to positively match. Only use this when the message generated by default doesn’t suit your needs.

Examples:

RSpec::Matchers.define :have_strength do |expected|
  match { your_match_logic }

  failure_message do |actual|
    "Expected strength of #{expected}, but had #{actual.strength}"
  end
end

Yields:

  • (Object)

    actual the actual object (i.e. the value wrapped by ‘expect`)

[ GitHub ]

  
# File 'rspec-expectations/lib/rspec/matchers/dsl.rb', line 216

def failure_message(&definition)
  define_user_override(__method__, definition)
end

#failure_message_when_negated {|Object| ... }

Customize the failure message to use when this matcher is asked to negatively match. Only use this when the message generated by default doesn’t suit your needs.

Examples:

RSpec::Matchers.define :have_strength do |expected|
  match { your_match_logic }

  failure_message_when_negated do |actual|
    "Expected not to have strength of #{expected}, but did"
  end
end

Yields:

  • (Object)

    actual the actual object (i.e. the value wrapped by ‘expect`)

[ GitHub ]

  
# File 'rspec-expectations/lib/rspec/matchers/dsl.rb', line 235

def failure_message_when_negated(&definition)
  define_user_override(__method__, definition)
end

#match(options = {}) {|Object| ... }

Stores the block that is used to determine whether this matcher passes or fails. The block should return a boolean value. When the matcher is passed to ‘expect(…).to` and the block returns true, then the expectation passes. Similarly, when the matcher is passed to expect(…).not_to and the block returns false, then the expectation passes.

By default the match block will swallow expectation errors (e.g. caused by using an expectation such as ‘expect(1).to eq 2`), if you wish to allow these to bubble up, pass in the option `:notify_expectation_failures => true`.

Examples:

RSpec::Matchers.define :be_even do
  match do |actual|
    actual.even?
  end
end

expect(4).to be_even     # passes
expect(3).not_to be_even # passes
expect(3).to be_even     # fails
expect(4).not_to be_even # fails

Parameters:

  • options (Hash) (defaults to: {})

    for defining the behavior of the match block.

Yields:

  • (Object)

    actual the actual value (i.e. the value wrapped by ‘expect`)

[ GitHub ]

  
# File 'rspec-expectations/lib/rspec/matchers/dsl.rb', line 131

def match(options={}, &match_block)
  define_user_override(:matches?, match_block) do |actual|
    @actual = actual
    RSpec::Support.with_failure_notifier(RAISE_NOTIFIER) do
      begin
        super(*actual_arg_for(match_block))
      rescue RSpec::Expectations::ExpectationNotMetError
        raise if options[:notify_expectation_failures]
        false
      end
    end
  end
end

#match_unless_raises(expected_exception = Exception) {|Object| ... }

Use this instead of ‘match` when the block will raise an exception rather than returning false to indicate a failure.

Examples:

RSpec::Matchers.define :accept_as_valid do |candidate_address|
  match_unless_raises ValidationException do |validator|
    validator.validate(candidate_address)
  end
end

expect(email_validator).to accept_as_valid("person@company.com")

Yields:

  • (Object)

    actual the actual object (i.e. the value wrapped by ‘expect`)

[ GitHub ]

  
# File 'rspec-expectations/lib/rspec/matchers/dsl.rb', line 188

def match_unless_raises(expected_exception=Exception, &match_block)
  define_user_override(:matches?, match_block) do |actual|
    @actual = actual
    begin
      super(*actual_arg_for(match_block))
    rescue expected_exception => @rescued_exception
      false
    else
      true
    end
  end
end

#match_when_negated(options = {}) {|Object| ... }

Use this to define the block for a negative expectation (‘expect(…).not_to`) when the positive and negative forms require different handling. This is rarely necessary, but can be helpful, for example, when specifying asynchronous processes that require different timeouts.

By default the match block will swallow expectation errors (e.g. caused by using an expectation such as ‘expect(1).to eq 2`), if you wish to allow these to bubble up, pass in the option `:notify_expectation_failures => true`.

Parameters:

  • options (Hash) (defaults to: {})

    for defining the behavior of the match block.

Yields:

  • (Object)

    actual the actual value (i.e. the value wrapped by ‘expect`)

[ GitHub ]

  
# File 'rspec-expectations/lib/rspec/matchers/dsl.rb', line 160

def match_when_negated(options={}, &match_block)
  define_user_override(:does_not_match?, match_block) do |actual|
    begin
      @actual = actual
      RSpec::Support.with_failure_notifier(RAISE_NOTIFIER) do
        super(*actual_arg_for(match_block))
      end
    rescue RSpec::Expectations::ExpectationNotMetError
      raise if options[:notify_expectation_failures]
      false
    end
  end
end

#supports_block_expectations

Declares that the matcher can be used in a block expectation. Users will not be able to use your matcher in a block expectation without declaring this. (e.g. ‘expect { do_something }.to matcher`).

[ GitHub ]

  
# File 'rspec-expectations/lib/rspec/matchers/dsl.rb', line 267

def supports_block_expectations
  define_method(:supports_block_expectations?) { true }
end