RSpec matchers combinators
Rspec matchers are a lot like predicates. Predicates that can talk … The good thing about predicates, as anybody who has done a bit of functional programming will tell you, is that they are easy to combine together into bigger predicates. I was really suprised to see that rspec does not come with such simple combinators as ‘and’ or ‘or’.
Here is a gist where I define simple combinators :
# Matcher to verify that all items match something else | |
RSpec::Matchers.define :all_ do |item_matcher| | |
match do |actual_items| | |
actual_items.all? { |item| item_matcher.matches?(item)} | |
end | |
description do | |
"#{item_matcher.description} to be true for all the items" | |
end | |
end | |
# Matcher to verify that at least one item matches something else | |
RSpec::Matchers.define :have_one_that do |item_matcher| | |
match do |actual_items| | |
actual_items.any? { |item| item_matcher.matches?(item)} | |
end | |
description do | |
"#{item_matcher.description} to be true for at least one item" | |
end | |
end | |
# Matcher to verify that an item matches all matchers in a list | |
RSpec::Matchers.define :and_ do |*matchers| | |
match do |actual| | |
matchers.all? {|matcher| matcher.matches?(actual) } | |
end | |
description do | |
(matchers.map &:description).join(" and ") | |
end | |
end |
With this and the email_spec matchers, it is possible to write something like this to find if an email was sent by rails :
expect(all_emails).to have_any_that(and_(deliver_to(receiver),
have_subject(subject),
have_body_text(body)))
It enables expressive test code and high reusability.
Using the ‘all’ combinator it is possible to write things like that :
expect(sample_items_attributes).to all_ have_key(:price)
I had to prepend ‘all’ and ‘and’ with an underscore because I would otherwise get conflicts… I’m not overly satisfied with this, but it will do for the moment.
I was happily supprised by the readability of error messages when the mach fails. Rspec does a very good job about this. I think it would be possible to get even better error messages by explicity using the sub matchers messages though. If anyone is interested, help yourself !
Leave a comment