Skip to content

Instantly share code, notes, and snippets.

@jasonkarns
Created July 29, 2015 15:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jasonkarns/0b4c9483d11be82bae56 to your computer and use it in GitHub Desktop.
Save jasonkarns/0b4c9483d11be82bae56 to your computer and use it in GitHub Desktop.
Custom Matcher that extends RSpec::Matchers::BuiltIn::Match to also enable specifying capture groups.
module CustomMatchers
class Match < RSpec::Matchers::BuiltIn::Match
def matches?(actual)
# first ensure the regex matched
return false unless result = super
# only continue if specifying captures
return result unless expected_captures = @captures
actual_captures = to_hash result
RSpec::Matchers::BuiltIn::Include.new(expected_captures).matches?(actual_captures).tap do |captures_matched|
# replace expected/actual regex with the matchdata
# if the matchdata didn't match. this way we get a diff of the matchdata hashes
# otherwise, leave untouched so the description is per normal
unless captures_matched
@expected = expected_captures
@actual = actual_captures
end
end
end
def capturing(captures)
@captures = captures
self
end
private
def to_hash(matchdata)
Hash[matchdata.names.map(&:to_sym).zip matchdata.captures]
end
end
def matchy(expected)
Match.new expected
end
end
@jasonkarns
Copy link
Author

This custom matcher inherits from RSpec::Matchers::BuiltIn::Match and adds the method capturing that enables specifying expected capture groups from the regex.

It works be first invoking the normal matches? behavior with super. If that fails, it exits early with the normal Match error messaging. Then it checks if there are any specified captures; if not it exits early.

If the match succeeded and there are capture expectations, then we convert the actual captures from a MatchData into a hash by zipping the capture group names and the capture values. (This initial attempt assumes named captures and does not support verifying indexed captures.)

Then we leverage RSpec's built-in Include matcher to see if the actual captures match the expected captures. If it does match, then we're done and RSpec can emit the description based on the regex/string.

If the captures do not match, then we know that RSpec will be emitting an error description based on the @actual and @expected ivars. Since the actual regex has already been proven to match, the error description would be more useful if it show the expected/actual captures instead of the regex and string. So we overwrite the actual/expected ivars with the captures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment