module RSpec::Matchers::DSL::Macros
Contains the methods that are available from within the ‘RSpec::Matchers.define` DSL
for creating custom matchers.
Constants
- RAISE_NOTIFIER
@private
Public Instance Methods
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.
@example
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")
# File lib/rspec/matchers/dsl.rb, line 297 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
Customize the description to use for one-liners. Only use this when the description generated by default doesn’t suit your needs.
@example
RSpec::Matchers.define :qualify_for do |expected| match { your_match_logic } description do "qualify for #{expected}" end end
@yield [Object] actual the actual object (i.e. the value wrapped by ‘expect`)
# File lib/rspec/matchers/dsl.rb, line 252 def description(&definition) define_user_override(__method__, definition) end
Tells the matcher to diff the actual and expected values in the failure message.
# File lib/rspec/matchers/dsl.rb, line 258 def diffable define_method(:diffable?) { true } end
Customizes the failure messsage to use when this matcher is asked to positively match. Only use this when the message generated by default doesn’t suit your needs.
@example
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
@yield [Object] actual the actual object (i.e. the value wrapped by ‘expect`)
# File lib/rspec/matchers/dsl.rb, line 215 def failure_message(&definition) define_user_override(__method__, definition) end
Customize the failure messsage to use when this matcher is asked to negatively match. Only use this when the message generated by default doesn’t suit your needs.
@example
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
@yield [Object] actual the actual object (i.e. the value wrapped by ‘expect`)
# File lib/rspec/matchers/dsl.rb, line 234 def failure_message_when_negated(&definition) define_user_override(__method__, definition) end
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.
@example
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
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 with to allow these to bubble up, pass in the option `:notify_expectation_failures => true`.
@param [Hash] options for defining the behavior of the match block. @yield [Object] actual the actual value (i.e. the value wrapped by ‘expect`)
# File lib/rspec/matchers/dsl.rb, line 130 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
Use this instead of ‘match` when the block will raise an exception rather than returning false to indicate a failure.
@example
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")
@yield [Object] actual the actual object (i.e. the value wrapped by ‘expect`)
# File lib/rspec/matchers/dsl.rb, line 187 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
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 with to allow these to bubble up, pass in the option `:notify_expectation_failures => true`.
@param [Hash] options for defining the behavior of the match block. @yield [Object] actual the actual value (i.e. the value wrapped by ‘expect`)
# File lib/rspec/matchers/dsl.rb, line 159 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
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`).
# File lib/rspec/matchers/dsl.rb, line 266 def supports_block_expectations define_method(:supports_block_expectations?) { true } end
Private Instance Methods
# File lib/rspec/matchers/dsl.rb, line 311 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
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 overriden 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.
# File lib/rspec/matchers/dsl.rb, line 345 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