To me, the real value of using matcher objects rather than simple assert_xyz methods is the extra power matcher objects give you that simple methods don't. In particularly, hamcrest, discussed in GOOS, allows matchers to be fully composed so that you can express detailed intent through a combination of matchers. In rspec-expectations, we have some places where composition currently works:
expect {
record.save
}.to change { record.created_at }.from(nil).to(be_within(1.second).of(Time.now))
expect(list).to include(match(/foo/), match(/bar/))
...but it's not yet supported everywhere. For example, this expression doesn't work yet:
expect { |b|
foo(&b)
}.to yield_with_args(include(match(/foo/), match(/bar/)))
...which means "I expect foo to yield with a collection that includes an element matching the regex /foo/ and an element matching the regex /bar/". In this case, we're composing 3 different matchers.
I think it would be a great new feature for 3.0 to make all matchers fully composable. Things to consider:
expect {
record.save
}.to change { record.created_at }.from(nil).to(a_value_within(1.second).of(Time.now))
expect(list).to include(a_string_matching(/foo/), a_string_matching(/bar/))
expect { |b|
foo(&b)
}.to yield_with_args(a_collection_including(a_string_matching(/foo/), a_string_matching(/bar/)))
- If we want to provide those aliases, we can make a module like
RSpec::Matchers::ComposableAliases that defines aliases for each of the built-ins in this kind of voice, and provide a config API that will include the module. (I think I'd want it opt-in since the aliases will squat on a lot more name real estate that users might otherwise be able to use).
- We may want to add some additional matchers that help with boolean operations. E.g. a negation matcher (so that you can wrap any matcher in the negation matcher and pass that as an argument), an
and matcher and an or matcher. We'd have to figure out the right phrasing for all these.
- We'd want the failure messages to read well in these cases, and not simply include the
inspect output of the composed matchers.
Thoughts?
To me, the real value of using matcher objects rather than simple
assert_xyzmethods is the extra power matcher objects give you that simple methods don't. In particularly, hamcrest, discussed in GOOS, allows matchers to be fully composed so that you can express detailed intent through a combination of matchers. In rspec-expectations, we have some places where composition currently works:...but it's not yet supported everywhere. For example, this expression doesn't work yet:
...which means "I expect
footo yield with a collection that includes an element matching the regex/foo/and an element matching the regex/bar/". In this case, we're composing 3 different matchers.I think it would be a great new feature for 3.0 to make all matchers fully composable. Things to consider:
===rather than==for this, as===means "matches" in ruby, where as==means "equals", and===makes much more sense (IMO).expect().toexpression, but poor when composed as an argument to another matcher. For example, for the examples above, I think this reads better:RSpec::Matchers::ComposableAliasesthat defines aliases for each of the built-ins in this kind of voice, and provide a config API that will include the module. (I think I'd want it opt-in since the aliases will squat on a lot more name real estate that users might otherwise be able to use).andmatcher and anormatcher. We'd have to figure out the right phrasing for all these.inspectoutput of the composed matchers.Thoughts?