rspec-expectations
rspec-expectations copied to clipboard
Provide both strict (true/false) and loose (truthy/falsey) forms of the be/have matchers
Maybe we can have both strict (true/false) and loose (truthy/falsey) forms of the be/have matchers?
That way, regardless of which is the "default", it is easy to do inline adjustment
some syntax possibilities:
- picking a symbol to append e.g.
be_infinitevsbe_infinite?orhave_foovshave_foo! - adding an option to the matcher e.g.
be_ready(strict: false) - coming up with another prefix (couldn't think of one)
alternatively there could be an explicitly truthy/falsey matcher
expect(number).to reply(:infinite?)
expect(number).to respond_truthy(:infinite?)
Originally posted by @fledman in https://github.com/rspec/rspec-mocks/issues/1218#issuecomment-833027284
@fledman the problem with passing a strict argument to predicate matchers is that the underlying predicate can accept parameters as well, and we pass them over to it, e.g. have_key(:foo)/be_multiple_of(3).
Context for strict_predicate_matchers and predicate matchers returning something outside of true/false:
- https://github.com/rspec/rspec-expectations/issues/1102
- https://bugs.ruby-lang.org/issues/9123
- https://github.com/rspec/rspec-expectations/pull/1196
- https://github.com/rspec/rspec-expectations/pull/1277
- https://github.com/rubocop/rubocop-rspec/issues/919
I'd prefer either adding a block that flips the config e.g you default to strict but run certain examples with:
around(:example) { |ex| use_truthy_predicate_matchers { ex.call } }
or setting certain predicate matcher override modes
config.set_strict_predicate_matcher_mode :be_infinite, :truthy
etc
if a strict kwarg won't work, then my preference is a new truthy/falsey matcher
since I would prefer to impact a single assertion, rather than an example, a context, or the whole suite
answer is another possible name, in addition to reply or respond_truthy
(although I am not in love with any of the three)
semantics:
expect(obj).to answer(method)- fails if obj does not respond to method
- calls obj.public_send(method)
- fails if the method call raised an exception
- passes if the return value is truthy
expect(obj).not_to answer(method)- fails if obj does not respond to method
- calls obj.public_send(method)
- fails if the method call raised an exception
- passes if the return value is falsey
examples:
# passing
expect(123).not_to answer(:infinite?)
expect(-Float::INFINITY).to answer(:infinite?)
expect([1]).to answer(:first)
expect({a:{b:{c:123}}}).to answer(:dig, :a, :b, :c)
# failing
expect(123).to answer(:infinite?)
expect(-Float::INFINITY).not_to answer(:infinite?)
expect('words').to answer(:infinite?)
expect('words').not_to answer(:infinite?)
expect([]).to answer(:first)
expect({}).to answer(:dig, :a, :b, :c)
I love the ideas of @JonRowe or a new matcher.
We kept be_truthy/be_falsey in 4.0, so the following is still possible:
# passes
expect(123.infinite?).to be_falsey
expect(-Float::INFINITY.infinite?).to be_truthy
However, it's not obvious which predicates can return non-boolean values in Ruby. I don't have a definitive list for core/stdlib, and can only name nonzero? and infinite? off the top of my head (broader list). And gems can have their pearls, too.
We may issue a warning when a predicate matcher is used, and the return value is neither true nor false, i.e.:
expect(123).to be_infinite
# Warning: `infitine?` predicate method called on `123` returned a non-boolean value. Consider using a less strict matcher instead `expect(123.infinite?).to be_truthy`
Added a warning here:
expect(-Float::INFINITY).to be_infinite
`infinite?` returned neither `true` nor `false`, but rather `-1`
for easier migration to strict mode ones.
config.set_strict_predicate_matcher_mode :be_infinite, :truthy
I love the idea.