-
-
Notifications
You must be signed in to change notification settings - Fork 394
Deprecates the have, have_at_least, have_at_most matchers #309
Deprecates the have, have_at_least, have_at_most matchers #309
Conversation
@myronmarston Please take a look at this. |
@@ -460,6 +460,8 @@ def exist(*args) | |||
# # Passes if "this string".length == 11 | |||
# expect("this string").to have(11).characters #"characters" is pure sugar | |||
def have(n) | |||
RSpec.deprecate("`have`", :replacement => "the rspec-collection_matchers gem") | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few things:
- By putting this here, it causes a deprecation warning to be printed if
have
is called, but not ifBuildIn::Have
is used directly. Whilehave
is the main public API, it's possible that there may be extension gems that use the matcher class directly since they may have code in a context where they don't want to mixinRSpec::Matchers
to gain access to the matcher API methods likehave
. I'd prefer to see this warning moved intoBuiltIn::Have
so that it is printed regardless of if they use it directly or use this API. - The
rspec-collection_matchers
gem is not the only replacement. User can also rewrite an expression likeexpect(array).to have(10).items
toexpect(array.count).to eq(10)
. I'd like for the deprecation warning to mention that they can either install your gem or change to that kind of expression. Ideally, the deprecation warning would smartly print the exact expression they can use instead. (taking into account owned vs unowned cases)...which suggests it'll have to be in the matcher, anyway, so that it can be a bit smarter. - If users install your gem will they still get this deprecation warning? It's important that they don't, because what we'll be telling users is "upgrade to 2.99, get your spec suite warning-free, then upgrade to 3.0" -- so it's important that when they do the right thing (e.g. switch to your gem), they no longer receive a warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started working on that and I wrote the following specs:
context "deprecations" do
it "prints a specific message when the target is a collection" do
replacement_message = "the rspec-collection_matchers gem " +
"or replace your expectation with something like " +
"`expect([1, 2, 3].size).to eq(3)`"
expect(RSpec).to receive(:deprecate).with("`have`", :replacement => replacement_message)
expect([1, 2, 3]).to have(3).items
end
it "prints a specific message when the target owns a collection" do
class BagOfWords
attr_reader :words
def initialize(words)
@words = words
end
end
replacement_message = "the rspec-collection_matchers gem " +
"or replace your expectation with something like " +
"`expect(your_collection.words).to eq(3)`"
expect(RSpec).to receive(:deprecate).with("`have`", :replacement => replacement_message)
bag = BagOfWords.new(%w(foo bar baz))
expect(bag).to have(3).words
end
end
I was able to make those specs green but I'm having problems when the expectation is in the negative form, like:
expect(bag).to_not have(3).words
Since the matcher instance doesn't have a reference to the ExpectationHandler
that called it, it can't know if the expectation is in the positive (expect(...).to
) or negative (expect(...).to_not
) form, so it can't know if the hint in the deprecation message should be expect(your_collection.words).to eq(3)
or expect(your_collection.words).to_not eq(3)
for example.
Do you have any suggestion on how to solve that problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the matcher instance doesn't have a reference to the ExpectationHandler that called it, it can't know if the expectation is in the positive (expect(...).to) or negative (expect(...).to_not) form, so it can't know if the hint in the deprecation message should be expect(your_collection.words).to eq(3) or expect(your_collection.words).to_not eq(3) for example.
This is exactly why the matcher protocol includes an optional does_not_match?
method for when you need slightly different logic for the negative case (beyond simply negating matches?
). For an example, see the respond_to
matcher:
rspec-expectations/lib/rspec/matchers/built_in/respond_to.rb
Lines 10 to 17 in 4ccacb8
def matches?(actual) | |
find_failing_method_names(actual, :reject).empty? | |
end | |
alias == matches? | |
def does_not_match?(actual) | |
find_failing_method_names(actual, :select).empty? | |
end |
It'd be good to get some spec's asserting on the output too :) |
…:Have is being used
@myronmarston @JonRowe Please take a look again. I addressed your suggestions. |
@@ -29,13 +32,20 @@ def matches?(collection_or_owner) | |||
for query_method in QUERY_METHODS | |||
next unless collection.respond_to?(query_method) | |||
@actual = collection.__send__(query_method) | |||
break unless @actual.nil? | |||
|
|||
if !@actual.nil? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just if @actual
? Seems simpler and more idiomatic. What you have here amounts to a double negative.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done on e35f610
I would like to give you an update about this PR. Sorry for did not have finished this yet. I've been very busy these last two weeks and I'll be busy at least for one more week. I think in 2 weeks I'll be able to continue working on that. I would like to know if there's a problem with that, regarding RSpec's 2.99 and 3 release planning. |
I don't think thats a problem... We're unlikly to get close to releasing 2.99 or 3 in 2 weeks :) Thanks for the hard work :) |
@hugobarauna -- where are things at for this PR? Would be great to get this wrapped up soon. |
@myronmarston Well asked, I've been very busy the last weeks, but I'll continue to work on that PR this week, probably tomorrow. |
…tations into 2-99-maintenance Conflicts: Changelog.md
Guys, I already did some of the fixes you've asked. I'm planning to continue working on that the day after tomorrow. I think I'll be able to finish the whole PR till the end of the week. |
deprecation_message << "or replace your expectation with something like " | ||
deprecation_message << "`expect(#{cardinality_expression(query_method)}).#{expectation_format_method} #{matcher_expression}`" | ||
|
||
RSpec.deprecate("`#{matcher_method}`", :replacement => deprecation_message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that the matcher method on its own doesn't give much context. Instead, I think it's better if the deprecation included a matcher expression, e.g.:
expression = if @negative_expectation
RSpec::Expectations::Syntax.negative_expression(cardinality_expression(query_method), old_matcher_expression)
else
RSpec::Expectations::Syntax.positive_expression(cardinality_expression(query_method), old_matcher_expression)
end
RSpec.deprecate("`#{expression}`", :replacement => deprecation_message)
The negative_expression
and positive_expression
methods take care of constructing a .should
or expect
expression based on configured settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to make sure I understood, when someone writes this: expect(owner).to have(3).items
, he should get a deprecation message like that: expect(your_object).to have(3).items is deprecated. Use the rspec-collection_matchers gem or replace your expectation with something like expect(your_object.items.count).to eq(3)
right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done on 7bbb4c9. Take a look at it and tell me if it's ok.
@myronmarston @JonRowe guys, please take a look again. I tried to address all of your suggestions. |
Think my concerns were addressed so deferring to @myronmarston |
Deprecates the have, have_at_least, have_at_most matchers
Thanks, @hugobarauna -- these deprecation messages will be very useful and clear for our users! |
…ugobarauna/2-99-maintenance Deprecates the have, have_at_least, have_at_most matchers --- This commit was imported from rspec/rspec-expectations@591dd95.
As planned for the RSpec 3.0 release, the
have
,have_at_least
andhave_at_most
matchers aren't going to be part of the default RSpec matchers set. So, that PR deprecates those matchers.This PR is related to #307