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

Deprecates the have, have_at_least, have_at_most matchers #309

Merged
merged 15 commits into from
Oct 18, 2013
Merged

Deprecates the have, have_at_least, have_at_most matchers #309

merged 15 commits into from
Oct 18, 2013

Conversation

hugobarauna
Copy link
Member

As planned for the RSpec 3.0 release, the have, have_at_least and have_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

  • move the deprecations to the have matcher
  • print smart deprecation messages
  • do not print deprecations if the the rspec-collection_matchers gem is being used
  • refactor code following Myron's suggestions

@hugobarauna
Copy link
Member Author

@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")

Copy link
Member

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 if BuildIn::Have is used directly. While have 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 mixin RSpec::Matchers to gain access to the matcher API methods like have. I'd prefer to see this warning moved into BuiltIn::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 like expect(array).to have(10).items to expect(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.

Copy link
Member Author

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?

Copy link
Member

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:

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

@JonRowe
Copy link
Member

JonRowe commented Aug 19, 2013

It'd be good to get some spec's asserting on the output too :)

@hugobarauna
Copy link
Member Author

@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?
Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done on e35f610

@hugobarauna
Copy link
Member Author

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.

@JonRowe
Copy link
Member

JonRowe commented Sep 14, 2013

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

@myronmarston
Copy link
Member

@hugobarauna -- where are things at for this PR? Would be great to get this wrapped up soon.

@hugobarauna
Copy link
Member Author

@myronmarston Well asked, I've been very busy the last weeks, but I'll continue to work on that PR this week, probably tomorrow.

@hugobarauna
Copy link
Member Author

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)
Copy link
Member

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.

Copy link
Member Author

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?

Copy link
Member Author

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.

@hugobarauna
Copy link
Member Author

@myronmarston @JonRowe guys, please take a look again. I tried to address all of your suggestions.

@JonRowe
Copy link
Member

JonRowe commented Oct 18, 2013

Think my concerns were addressed so deferring to @myronmarston

myronmarston added a commit that referenced this pull request Oct 18, 2013
Deprecates the have, have_at_least, have_at_most matchers
@myronmarston myronmarston merged commit 591dd95 into rspec:2-99-maintenance Oct 18, 2013
@myronmarston
Copy link
Member

Thanks, @hugobarauna -- these deprecation messages will be very useful and clear for our users!

@hugobarauna hugobarauna deleted the 2-99-maintenance branch October 18, 2013 13:28
yujinakayama pushed a commit to yujinakayama/rspec-monorepo that referenced this pull request Oct 6, 2021
…ugobarauna/2-99-maintenance

Deprecates the have, have_at_least, have_at_most matchers

---
This commit was imported from rspec/rspec-expectations@591dd95.
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

Successfully merging this pull request may close these issues.

None yet

3 participants