Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide both strict (true/false) and loose (truthy/falsey) forms of the be/have matchers #1300

Open
pirj opened this issue May 6, 2021 · 7 comments

Comments

@pirj
Copy link
Member

pirj commented May 6, 2021

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:

  1. picking a symbol to append e.g. be_infinite vs be_infinite? or have_foo vs have_foo!
  2. adding an option to the matcher e.g. be_ready(strict: false)
  3. 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 rspec/rspec-mocks#1218 (comment)

@pirj
Copy link
Member Author

pirj commented May 6, 2021

@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:

@JonRowe
Copy link
Member

JonRowe commented May 6, 2021

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

@fledman
Copy link

fledman commented May 6, 2021

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

@fledman
Copy link

fledman commented May 6, 2021

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)

@benoittgt
Copy link
Member

I love the ideas of @JonRowe or a new matcher.

@pirj
Copy link
Member Author

pirj commented May 11, 2021

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`

@pirj
Copy link
Member Author

pirj commented Jul 23, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants