Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Make all matchers composable for 3.0 #280

Description

@myronmarston

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?

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions