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

Print deprecation message when implicit block expectation syntax is used #1139

Conversation

pirj
Copy link
Member

@pirj pirj commented Oct 29, 2019

The purpose of this (rather controversial) change is to discourage the usage of a so-called implicit block expectation syntax:

subject { -> { do_something } }
it { is_expected.to change(something).to(new_value) }

In case this syntax is being used, a deprecation warning will be printed.

This syntax was referenced to as:

pretty obtuse and not something I’d recommend, generally.
#805 (comment)

We should mention they’re not supported.
#805 (comment)

From rspec-core source:

The one-liner syntax only works with non-block expectations (e.g. expect(obj).to eq, etc) and it cannot be used with block expectations (e.g. expect { object }).

This is a less strict follow-up to #1125, which was released as part of a patch release 3.8.5, and later reverted in 3.8.6 due to a number of complaints.
Even though workarounds are possible for the most desperate usages of this syntax, introducing a deprecation warning in pre-4.0 version of RSpec and removal of (coincidental, but still) support for the implicit block syntax in a major release makes more sense than removal of it in a minor/patch version, since it's used quite often across different codebases I've seen.

Example Deprecation Message

Deprecation Warnings:

You must pass a block rather than an argument to `expect` to use the provided block expectation matcher (change `1`), or the matcher must implement `supports_value_expectations?`.

I'm not quite certain it's the best one possible, since it does not refer to the code that led to this deprecation message.
Appreciate any hint how to make it better.

Additional Things to Notice

According to #1125 (comment), the following works with no disruptions:

RSpec.describe 'introspect' do
  it do
    expect(-> {}).to be_a(Proc)
  end
end

Related Links

#526
#530
#536
#1066
#1125

https://blog.rubystyle.guide/rspec/2019/07/17/rspec-implicit-block-syntax.html
https://www.reddit.com/r/ruby/comments/cejl3q/call_for_discussion_rspec_implicit_block
rubocop/rspec-style-guide#76
rubocop/rubocop-rspec#789

Example fix for matcher libraries that are forced to keep the interface to provide the syntax for their users rodjek/rspec-puppet#767

@pirj pirj self-assigned this Oct 29, 2019
@pirj pirj requested a review from JonRowe October 29, 2019 14:42
@pirj pirj changed the title The purpose of this (rather controversial) change is to discourage the usage of a so-called implicit block expectation syntax: Print deprecation message when implicit block expectation syntax is used Oct 30, 2019
Copy link
Member

@JonRowe JonRowe left a comment

Choose a reason for hiding this comment

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

Sorry for the delay @pirj got a bit exhausted by this change then forgot about!

Changelog.md Outdated Show resolved Hide resolved
lib/rspec/expectations/expectation_target.rb Outdated Show resolved Hide resolved
Changelog.md Show resolved Hide resolved
lib/rspec/expectations/expectation_target.rb Outdated Show resolved Hide resolved
lib/rspec/expectations/expectation_target.rb Outdated Show resolved Hide resolved
lib/rspec/matchers/built_in/throw_symbol.rb Outdated Show resolved Hide resolved
spec/spec_helper.rb Outdated Show resolved Hide resolved
lib/rspec/matchers/built_in/yield.rb Outdated Show resolved Hide resolved
lib/rspec/matchers/built_in/yield.rb Outdated Show resolved Hide resolved
lib/rspec/matchers/built_in/yield.rb Outdated Show resolved Hide resolved
@pirj pirj marked this pull request as draft April 27, 2020 21:10
@JonRowe JonRowe changed the base branch from master to main August 2, 2020 02:06
@pirj
Copy link
Member Author

pirj commented Nov 14, 2020

@JonRowe Is it better finishing this before we release 4.0? Or do you think it will be ok to introduce this warning in 4.0.x and remove in 4.1.0?

@JonRowe
Copy link
Member

JonRowe commented Nov 15, 2020

Or do you think it will be ok to introduce this warning in 4.0.x and remove in 4.1.0?

No that would be a breaking change, any removal would be in a major version.

@pirj
Copy link
Member Author

pirj commented Nov 15, 2020

Sounds good 👍
I intend to wrap this PR up for 3.11 or 3.12, and handle the removal for 4.0.

Changelog.md Show resolved Hide resolved
spec/rspec/matchers_spec.rb Outdated Show resolved Hide resolved
lib/rspec/matchers/built_in/change.rb Outdated Show resolved Hide resolved
lib/rspec/expectations/expectation_target.rb Outdated Show resolved Hide resolved
lib/rspec/matchers/built_in/yield.rb Outdated Show resolved Hide resolved
@pirj pirj force-pushed the warn-on-block-matchers-being-used-with-value-expectation-target branch 2 times, most recently from 0a6314b to f95bb8b Compare November 30, 2020 15:03
@pirj pirj marked this pull request as ready for review November 30, 2020 15:10
@pirj pirj force-pushed the warn-on-block-matchers-being-used-with-value-expectation-target branch 3 times, most recently from 1039699 to 734c46b Compare November 30, 2020 15:25
@pirj pirj dismissed JonRowe’s stale review November 30, 2020 15:30

Code review notes addressed

@pirj
Copy link
Member Author

pirj commented Nov 30, 2020

The one and only diff-lcs example failure on Travis seems unrelated.

@pirj pirj force-pushed the warn-on-block-matchers-being-used-with-value-expectation-target branch from 5f18fa7 to e9375db Compare December 2, 2020 00:29
end

it 'prints a deprecation warning when given a value' do
expect_warn_deprecation(/You must pass a block/)
Copy link
Member Author

Choose a reason for hiding this comment

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

@pirj pirj force-pushed the warn-on-block-matchers-being-used-with-value-expectation-target branch from 33a8a53 to 8c574e0 Compare December 2, 2020 17:20
@pirj

This comment has been minimized.

@JonRowe
Copy link
Member

JonRowe commented Dec 2, 2020

I'll try to review this at the weekend, its too large to do during this week with my work schedule atm.

@pirj pirj force-pushed the warn-on-block-matchers-being-used-with-value-expectation-target branch from aaa9683 to ae5ff16 Compare December 2, 2020 21:37
@rspec rspec deleted a comment from pirj Dec 12, 2020
@rspec rspec deleted a comment from pirj Dec 12, 2020
@JonRowe
Copy link
Member

JonRowe commented Dec 12, 2020

@pirj this is still on my to do list don't worry, it's just lower than rspec-rails 6.1 support (please don't send ping comments)

pirj added a commit that referenced this pull request Jan 29, 2021
https://blog.rubystyle.guide/rspec/2019/07/17/rspec-implicit-block-syntax.html
https://rspec.rubystyle.guide/#implicit-block-expectations

Spec changes are due to:

    matcher.matches?(invalid_value)

doesn't work with block-only matchers, as no block is passed in, and it
fails with a message:

    1) RSpec::Matchers::BuiltIn::Change behaves like an RSpec block-only matcher uses the `ObjectFormatter` for `failure_message`
       Failure/Error: expect(message).to include("detailed inspect")
         expected "expected `@k` to have changed, but was not given a block" to include "detailed inspect"

The redundant (due to existing check in ExpectationTarget) `Proc ===
@event_proc` checks could not be removed safely as well, since
@actual_after is not initialized yet when we haven't executed the block:

     RuntimeError:
       Warnings were generated: rspec-dev/repos/rspec-expectations/lib/rspec/matchers/built_in/change.rb:407: warning: instance variable @actual_after not initialized

Also see:
  #1139
  #1125
pirj added a commit that referenced this pull request Jan 29, 2021
https://blog.rubystyle.guide/rspec/2019/07/17/rspec-implicit-block-syntax.html
https://rspec.rubystyle.guide/#implicit-block-expectations

Spec changes are due to:

    matcher.matches?(invalid_value)

doesn't work with block-only matchers, as no block is passed in, and it
fails with a message:

    1) RSpec::Matchers::BuiltIn::Change behaves like an RSpec block-only matcher uses the `ObjectFormatter` for `failure_message`
       Failure/Error: expect(message).to include("detailed inspect")
         expected "expected `@k` to have changed, but was not given a block" to include "detailed inspect"

The redundant (due to existing check in ExpectationTarget) `Proc ===
@event_proc` checks could not be removed safely as well, since
@actual_after is not initialized yet when we haven't executed the block:

     RuntimeError:
       Warnings were generated: rspec-dev/repos/rspec-expectations/lib/rspec/matchers/built_in/change.rb:407: warning: instance variable @actual_after not initialized

Also see:
  #1139
  #1125
pirj added a commit that referenced this pull request Jan 29, 2021
https://blog.rubystyle.guide/rspec/2019/07/17/rspec-implicit-block-syntax.html
https://rspec.rubystyle.guide/#implicit-block-expectations

Spec changes are due to:

    matcher.matches?(invalid_value)

doesn't work with block-only matchers, as no block is passed in, and it
fails with a message:

    1) RSpec::Matchers::BuiltIn::Change behaves like an RSpec block-only matcher uses the `ObjectFormatter` for `failure_message`
       Failure/Error: expect(message).to include("detailed inspect")
         expected "expected `@k` to have changed, but was not given a block" to include "detailed inspect"

The redundant (due to existing check in ExpectationTarget) `Proc ===
@event_proc` checks could not be removed safely as well, since
@actual_after is not initialized yet when we haven't executed the block:

     RuntimeError:
       Warnings were generated: rspec-dev/repos/rspec-expectations/lib/rspec/matchers/built_in/change.rb:407: warning: instance variable @actual_after not initialized

Also see:
  #1139
  #1125
@pirj pirj force-pushed the warn-on-block-matchers-being-used-with-value-expectation-target branch from 3a0e06f to f6a761b Compare February 2, 2021 22:03
@JonRowe
Copy link
Member

JonRowe commented Feb 5, 2021

Ok! I have finally reviewed this. I have the following concerns:

  • That this change is going to be unwelcome based on previous feedback.

I think I'm happy to proceed regardless however, as we can always walk it back if we get enough feedback, and most of these changes are good anyway.

  • That there is no example of the deprecated code in the deprecation.

e.g.

The implicit block expectation syntax is deprecated, you should pass a block rather than an argument to expect to use the provided block expectation matcher (raise Exception), or the matcher must implement supports_value_expectations?.

I would prefer something like the suggestion I've applied.

https://blog.rubystyle.guide/rspec/2019/07/17/rspec-implicit-block-syntax.html
https://rspec.rubystyle.guide/#implicit-block-expectations

Spec changes are due to:

    matcher.matches?(invalid_value)

doesn't work with block-only matchers, as no block is passed in, and it
fails with a message:

    1) RSpec::Matchers::BuiltIn::Change behaves like an RSpec block-only matcher uses the `ObjectFormatter` for `failure_message`
       Failure/Error: expect(message).to include("detailed inspect")
         expected "expected `@k` to have changed, but was not given a block" to include "detailed inspect"

The redundant (due to existing check in ExpectationTarget) `Proc ===
@event_proc` checks could not be removed safely as well, since
@actual_after is not initialized yet when we haven't executed the block:

     RuntimeError:
       Warnings were generated: rspec-dev/repos/rspec-expectations/lib/rspec/matchers/built_in/change.rb:407: warning: instance variable @actual_after not initialized

Ruby 1.8-specific workarounds:

    multiple values for a block parameter (0 for 1)

If a block expects an argument, it ought to be provided an argument in 1.8
@pirj pirj force-pushed the warn-on-block-matchers-being-used-with-value-expectation-target branch from 6e91f8b to fe0c4b7 Compare February 7, 2021 15:51
pirj added a commit that referenced this pull request Feb 7, 2021
pirj added a commit that referenced this pull request Feb 7, 2021
https://blog.rubystyle.guide/rspec/2019/07/17/rspec-implicit-block-syntax.html
https://rspec.rubystyle.guide/#implicit-block-expectations

Spec changes are due to:

    matcher.matches?(invalid_value)

doesn't work with block-only matchers, as no block is passed in, and it
fails with a message:

    1) RSpec::Matchers::BuiltIn::Change behaves like an RSpec block-only matcher uses the `ObjectFormatter` for `failure_message`
       Failure/Error: expect(message).to include("detailed inspect")
         expected "expected `@k` to have changed, but was not given a block" to include "detailed inspect"

The redundant (due to existing check in ExpectationTarget) `Proc ===
@event_proc` checks could not be removed safely as well, since
@actual_after is not initialized yet when we haven't executed the block:

     RuntimeError:
       Warnings were generated: rspec-dev/repos/rspec-expectations/lib/rspec/matchers/built_in/change.rb:407: warning: instance variable @actual_after not initialized

Also see:
  #1139
  #1125
@pirj
Copy link
Member Author

pirj commented Feb 7, 2021

Sounds good. Applied the suggestion here, and made a similar change in #1285

pirj added a commit that referenced this pull request Feb 7, 2021
https://blog.rubystyle.guide/rspec/2019/07/17/rspec-implicit-block-syntax.html
https://rspec.rubystyle.guide/#implicit-block-expectations

Spec changes are due to:

    matcher.matches?(invalid_value)

doesn't work with block-only matchers, as no block is passed in, and it
fails with a message:

    1) RSpec::Matchers::BuiltIn::Change behaves like an RSpec block-only matcher uses the `ObjectFormatter` for `failure_message`
       Failure/Error: expect(message).to include("detailed inspect")
         expected "expected `@k` to have changed, but was not given a block" to include "detailed inspect"

The redundant (due to existing check in ExpectationTarget) `Proc ===
@event_proc` checks could not be removed safely as well, since
@actual_after is not initialized yet when we haven't executed the block:

     RuntimeError:
       Warnings were generated: rspec-dev/repos/rspec-expectations/lib/rspec/matchers/built_in/change.rb:407: warning: instance variable @actual_after not initialized

Also see:
  #1139
  #1125
@pirj pirj requested a review from JonRowe February 7, 2021 16:03
@JonRowe JonRowe merged commit 9efd205 into rspec:main Feb 7, 2021
@JonRowe
Copy link
Member

JonRowe commented Feb 7, 2021

Thanks for your patience and hard work on this :)

JonRowe added a commit that referenced this pull request Feb 7, 2021
…with-value-expectation-target

Print deprecation message when implicit block expectation syntax is used
@pirj pirj deleted the warn-on-block-matchers-being-used-with-value-expectation-target branch February 7, 2021 22:36
pirj added a commit that referenced this pull request Feb 7, 2021
https://blog.rubystyle.guide/rspec/2019/07/17/rspec-implicit-block-syntax.html
https://rspec.rubystyle.guide/#implicit-block-expectations

Spec changes are due to:

    matcher.matches?(invalid_value)

doesn't work with block-only matchers, as no block is passed in, and it
fails with a message:

    1) RSpec::Matchers::BuiltIn::Change behaves like an RSpec block-only matcher uses the `ObjectFormatter` for `failure_message`
       Failure/Error: expect(message).to include("detailed inspect")
         expected "expected `@k` to have changed, but was not given a block" to include "detailed inspect"

The redundant (due to existing check in ExpectationTarget) `Proc ===
@event_proc` checks could not be removed safely as well, since
@actual_after is not initialized yet when we haven't executed the block:

     RuntimeError:
       Warnings were generated: rspec-dev/repos/rspec-expectations/lib/rspec/matchers/built_in/change.rb:407: warning: instance variable @actual_after not initialized

Also see:
  #1139
  #1125
pirj added a commit that referenced this pull request Feb 8, 2021
https://blog.rubystyle.guide/rspec/2019/07/17/rspec-implicit-block-syntax.html
https://rspec.rubystyle.guide/#implicit-block-expectations

Spec changes are due to:

    matcher.matches?(invalid_value)

doesn't work with block-only matchers, as no block is passed in, and it
fails with a message:

    1) RSpec::Matchers::BuiltIn::Change behaves like an RSpec block-only matcher uses the `ObjectFormatter` for `failure_message`
       Failure/Error: expect(message).to include("detailed inspect")
         expected "expected `@k` to have changed, but was not given a block" to include "detailed inspect"

The redundant (due to existing check in ExpectationTarget) `Proc ===
@event_proc` checks could not be removed safely as well, since
@actual_after is not initialized yet when we haven't executed the block:

     RuntimeError:
       Warnings were generated: rspec-dev/repos/rspec-expectations/lib/rspec/matchers/built_in/change.rb:407: warning: instance variable @actual_after not initialized

Also see:
  #1139
  #1125
guigs added a commit to guigs/rspec-collection_matchers that referenced this pull request Mar 4, 2021
With [this change](rspec/rspec-expectations#1139) in rspec-expectations, a call to `expect(my_object).to have(...)` is raising a `NoMethodError: undefined method `supports_value_expectations?' for <MyObject:0x000055a1b6e065c0>`. 

This is because ExpectationTarget calls `supports_value_expectation` here:

https://github.com/rspec/rspec-expectations/blob/4e7011fe8ac68b74621336b07f894879c8da202d/lib/rspec/expectations/expectation_target.rb#L127

Which ends up being processed by `Have#method_missing` which sets `supports_value_expectations?` to @collection_name. Later in `Have#determine_collection` it tries to call `supports_value_expectations?` on my_object, which causes the NoMethodError.

The fix is simple, just implement `Have#supports_value_expectations?`.
@pirj pirj added this to the 4.0 milestone Mar 12, 2021
yujinakayama pushed a commit to yujinakayama/rspec-monorepo that referenced this pull request Oct 6, 2021
By directly instantiating expectation handler, should/should_not was
circumventing enforce_value_expectation check.

    subject(:action) { -> { raise } }
    it { should raise_error StandardError }

would not print "The implicit block expectation syntax is deprecated",
while

    subject(:action) { -> { raise } }
    it { is_expected.to raise_error StandardError }

will.

See rspec/rspec-expectations#1139

---
This commit was imported from rspec/rspec-core@d04af61.
yujinakayama pushed a commit to yujinakayama/rspec-monorepo that referenced this pull request Oct 6, 2021
…pirj/warn-on-block-matchers-being-used-with-value-expectation-target

Print deprecation message when implicit block expectation syntax is used

---
This commit was imported from rspec/rspec-expectations@9efd205.
yujinakayama pushed a commit to yujinakayama/rspec-monorepo that referenced this pull request Oct 19, 2021
…pirj/warn-on-block-matchers-being-used-with-value-expectation-target

Print deprecation message when implicit block expectation syntax is used

---
This commit was imported from rspec/rspec-expectations@f656188.
yujinakayama pushed a commit to yujinakayama/rspec-monorepo that referenced this pull request Oct 19, 2021
https://blog.rubystyle.guide/rspec/2019/07/17/rspec-implicit-block-syntax.html
https://rspec.rubystyle.guide/#implicit-block-expectations

Spec changes are due to:

    matcher.matches?(invalid_value)

doesn't work with block-only matchers, as no block is passed in, and it
fails with a message:

    1) RSpec::Matchers::BuiltIn::Change behaves like an RSpec block-only matcher uses the `ObjectFormatter` for `failure_message`
       Failure/Error: expect(message).to include("detailed inspect")
         expected "expected `@k` to have changed, but was not given a block" to include "detailed inspect"

The redundant (due to existing check in ExpectationTarget) `Proc ===
@event_proc` checks could not be removed safely as well, since
@actual_after is not initialized yet when we haven't executed the block:

     RuntimeError:
       Warnings were generated: rspec-dev/repos/rspec-expectations/lib/rspec/matchers/built_in/change.rb:407: warning: instance variable @actual_after not initialized

Also see:
  rspec/rspec-expectations#1139
  rspec/rspec-expectations#1125

---
This commit was imported from rspec/rspec-expectations@aaf93ad.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants