module Spec
module Matchers
class SimpleMatcher
attr_writer :failure_message, :negative_failure_message, :description
def initialize(description, &match_block)
@description = description
@match_block = match_block
end
def matches?(given)
@given = given
case @match_block.arity
when 2
@match_block.call(@given, self)
else
@match_block.call(@given)
end
end
def description
@description || explanation
end
def failure_message
@failure_message || (@description.nil? ? explanation : %[expected #{@description.inspect} but got #{@given.inspect}])
end
def negative_failure_message
@negative_failure_message || (@description.nil? ? explanation : %[expected not to get #{@description.inspect}, but got #{@given.inspect}])
end
def explanation
"No description provided. See RDoc for simple_matcher()"
end
end
# simple_matcher makes it easy for you to create your own custom matchers
# in just a few lines of code when you don't need all the power of a
# completely custom matcher object.
#
# The description argument will appear as part of any failure
# message, and is also the source for auto-generated descriptions.
#
# The match_block can have an arity of 1 or 2. The first block
# argument will be the given value. The second, if the block accepts it
# will be the matcher itself, giving you access to set custom failure
# messages in favor of the defaults.
#
# The match_block should return a boolean: true
# indicates a match, which will pass if you use should and fail
# if you use should_not. false (or nil) indicates no match,
# which will do the reverse: fail if you use should and pass if
# you use should_not.
#
# An error in the match_block will bubble up, resulting in a
# failure.
#
# == Example with default messages
#
# def be_even
# simple_matcher("an even number") { |given| given % 2 == 0 }
# end
#
# describe 2 do
# it "should be even" do
# 2.should be_even
# end
# end
#
# Given an odd number, this example would produce an error message stating:
# expected "an even number", got 3.
#
# Unfortunately, if you're a fan of auto-generated descriptions, this will
# produce "should an even number." Not the most desirable result. You can
# control that using custom messages:
#
# == Example with custom messages
#
# def rhyme_with(expected)
# simple_matcher("rhyme with #{expected.inspect}") do |given, matcher|
# matcher.failure_message = "expected #{given.inspect} to rhyme with #{expected.inspect}"
# matcher.negative_failure_message = "expected #{given.inspect} not to rhyme with #{expected.inspect}"
# given.rhymes_with? expected
# end
# end
#
# # OR
#
# def rhyme_with(expected)
# simple_matcher do |given, matcher|
# matcher.description = "rhyme with #{expected.inspect}"
# matcher.failure_message = "expected #{given.inspect} to rhyme with #{expected.inspect}"
# matcher.negative_failure_message = "expected #{given.inspect} not to rhyme with #{expected.inspect}"
# given.rhymes_with? expected
# end
# end
#
# describe "pecan" do
# it "should rhyme with 'be gone'" do
# nut = "pecan"
# nut.extend Rhymer
# nut.should rhyme_with("be gone")
# end
# end
#
# The resulting messages would be:
# description: rhyme with "be gone"
# failure_message: expected "pecan" to rhyme with "be gone"
# negative failure_message: expected "pecan" not to rhyme with "be gone"
#
# == Wrapped Expectations
#
# Because errors will bubble up, it is possible to wrap other expectations
# in a SimpleMatcher.
#
# def be_even
# simple_matcher("an even number") { |given| (given % 2).should == 0 }
# end
#
# BE VERY CAREFUL when you do this. Only use wrapped expectations for
# matchers that will always be used in only the positive
# (should) or negative (should_not), but not both.
# The reason is that is you wrap a should and call the wrapper
# with should_not, the correct result (the should
# failing), will fail when you want it to pass.
#
def simple_matcher(description=nil, &match_block)
SimpleMatcher.new(description, &match_block)
end
end
end