Reputation: 25
For testing reasons I've recently moved some of my RSpec matchers to use the class form rather than the DSL. Is there a way to easily get chaining behaviour when they are in this form.
E.g.
class BeInZone
def initialize(expected)
@expected = expected
end
def matches?(target)
@target = target
@target.current_zone.eql?(Zone.new(@expected))
end
def failure_message
"expected #{@target.inspect} to be in Zone #{@expected}"
end
def negative_failure_message
"expected #{@target.inspect} not to be in Zone #{@expected}"
end
# chain methods here
end
Many thanks
Upvotes: 2
Views: 1496
Reputation: 6961
Add a new method with the name of chain, which normally should return self
. Typically you save the provided chained state. Which you then update the matches?
method to use. This state can also be used in the various output message methods too.
So for your example:
class BeInZone
# Your code
def matches?(target)
@target = target
matches_zone? && matches_name?
end
def with_name(name)
@target_name = name
self
end
private
def matches_zone?
@target.current_zone.eql?(Zone.new(@expected))
end
def matches_name?
true unless @target_name
@target =~ @target_name
end
end
Then to use it: expect(zoneA_1).to be_in_zone(zoneA).with_name('1')
The reason this works is that you are building the object that you are passing to either the should
or expect(object).to
methods. These methods then call matches?
on the provided object.
So it's no different than other ruby code like puts "hi there".reverse.upcase.gsub('T', '7')
. here the string "hi there"
is your matcher and the chained methods are called on it, passing the final object returned from gsub
to puts
.
The built-in expect change
matcher is a good example to review.
Upvotes: 4