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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Enhancements:

* Return `true` from `aggregate_failures` when no exception occurs. (Jon Rowe, #1225)

Deprecations:
* Print a deprecation message when using the implicit block expectation syntax.
(Phil Pirozhkov, #1139)

### 3.10.1 / 2020-12-27
[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.10.0...v3.10.1)

Expand Down Expand Up @@ -120,7 +124,7 @@ Bug Fixes:
* Prevent composed `all` matchers from leaking into their siblings leading to duplicate
failures. (Jamie English, #1086)
* Prevent objects which change their hash on comparison from failing change checks.
(Phil Pirozhkov, #1110)
(Phil Pirozhkov, #1100)
pirj marked this conversation as resolved.
Show resolved Hide resolved
* Issue an `ArgumentError` rather than a `NoMethodError` when `be_an_instance_of` and
`be_kind_of` matchers encounter objects not supporting those methods.
(Taichi Ishitani, #1107)
Expand Down
42 changes: 41 additions & 1 deletion lib/rspec/expectations/expectation_target.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def self.for(value, block)
elsif block
raise ArgumentError, "You cannot pass both an argument and a block to `expect`."
else
new(value)
ValueExpectationTarget.new(value)
JonRowe marked this conversation as resolved.
Show resolved Hide resolved
end
end

Expand Down Expand Up @@ -90,6 +90,46 @@ def prevent_operator_matchers(verb)
include InstanceMethods
end

# @private
# Validates the provided matcher to ensure it supports block
# expectations, in order to avoid user confusion when they
# use a block thinking the expectation will be on the return
# value of the block rather than the block itself.
class ValueExpectationTarget < ExpectationTarget
def to(matcher=nil, message=nil, &block)
enforce_value_expectation(matcher)
super
end

def not_to(matcher=nil, message=nil, &block)
enforce_value_expectation(matcher)
super
end

private

def enforce_value_expectation(matcher)
return if supports_value_expectations?(matcher)

RSpec.deprecate(
"expect(value).to #{RSpec::Support::ObjectFormatter.format(matcher)}",
:message =>
"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 or the matcher must implement " \
"`supports_value_expectations?`. e.g `expect { value }.to " \
"#{RSpec::Support::ObjectFormatter.format(matcher)}` not " \
"`expect(value).to #{RSpec::Support::ObjectFormatter.format(matcher)}`"
)
end

def supports_value_expectations?(matcher)
matcher.supports_value_expectations?
rescue NoMethodError
true
end
end

# @private
# Validates the provided matcher to ensure it supports block
# expectations, in order to avoid user confusion when they
Expand Down
5 changes: 5 additions & 0 deletions lib/rspec/matchers/built_in/base_matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ def supports_block_expectations?
false
end

# @private
def supports_value_expectations?
true
end

# @api private
def expects_call_stack_jump?
false
Expand Down
22 changes: 22 additions & 0 deletions lib/rspec/matchers/built_in/change.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ def supports_block_expectations?
true
end

# @private
def supports_value_expectations?
false
end

private

def initialize(receiver=nil, message=nil, &block)
Expand Down Expand Up @@ -158,6 +163,11 @@ def supports_block_expectations?
true
end

# @private
def supports_value_expectations?
false
end

private

def failure_reason
Expand Down Expand Up @@ -201,6 +211,11 @@ def supports_block_expectations?
true
end

# @private
def supports_value_expectations?
false
end

private

def perform_change(event_proc)
Expand Down Expand Up @@ -337,6 +352,8 @@ def change_description
class ChangeDetails
attr_reader :actual_after

UNDEFINED = Module.new.freeze

def initialize(matcher_name, receiver=nil, message=nil, &block)
if receiver && !message
raise(
Expand All @@ -351,6 +368,11 @@ def initialize(matcher_name, receiver=nil, message=nil, &block)
@receiver = receiver
@message = message
@value_proc = block
# TODO: temporary measure to mute warning of access to an initialized
# instance variable when a deprecated implicit block expectation
# syntax is used. This may be removed once `fail` is used, and the
# matcher never issues this warning.
@actual_after = UNDEFINED
end

def value_representation
Expand Down
14 changes: 14 additions & 0 deletions lib/rspec/matchers/built_in/compound.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,19 @@ def description
"#{matcher_1.description} #{conjunction} #{matcher_2.description}"
end

# @api private
def supports_block_expectations?
matcher_supports_block_expectations?(matcher_1) &&
matcher_supports_block_expectations?(matcher_2)
end

# @api private
def supports_value_expectations?
matcher_supports_value_expectations?(matcher_1) &&
matcher_supports_value_expectations?(matcher_2)
end

# @api private
def expects_call_stack_jump?
NestedEvaluator.matcher_expects_call_stack_jump?(matcher_1) ||
NestedEvaluator.matcher_expects_call_stack_jump?(matcher_2)
Expand Down Expand Up @@ -102,6 +110,12 @@ def matcher_supports_block_expectations?(matcher)
false
end

def matcher_supports_value_expectations?(matcher)
matcher.supports_value_expectations?
rescue NoMethodError
true
end

def matcher_is_diffable?(matcher)
matcher.diffable?
rescue NoMethodError
Expand Down
7 changes: 7 additions & 0 deletions lib/rspec/matchers/built_in/output.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ def supports_block_expectations?
true
end

# @api private
# Indicates this matcher matches against a block only.
# @return [False]
def supports_value_expectations?
false
end

private

def captured?
Expand Down
6 changes: 6 additions & 0 deletions lib/rspec/matchers/built_in/raise_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ def supports_block_expectations?
true
end

# @private
def supports_value_expectations?
false
end

# @private
def expects_call_stack_jump?
true
end
Expand Down
8 changes: 6 additions & 2 deletions lib/rspec/matchers/built_in/throw_symbol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,16 @@ def description
end

# @api private
# Indicates this matcher matches against a block.
# @return [True]
def supports_block_expectations?
true
end

# @api private
def supports_value_expectations?
false
end

# @api private
def expects_call_stack_jump?
true
end
Expand Down
20 changes: 20 additions & 0 deletions lib/rspec/matchers/built_in/yield.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ def supports_block_expectations?
true
end

# @private
def supports_value_expectations?
false
end

private

def failure_reason
Expand Down Expand Up @@ -169,6 +174,11 @@ def supports_block_expectations?
true
end

# @private
def supports_value_expectations?
false
end

private

def positive_failure_reason
Expand Down Expand Up @@ -231,6 +241,11 @@ def supports_block_expectations?
true
end

# @private
def supports_value_expectations?
false
end

private

def positive_failure_reason
Expand Down Expand Up @@ -328,6 +343,11 @@ def supports_block_expectations?
true
end

# @private
def supports_value_expectations?
false
end

private

def expected_arg_description
Expand Down
4 changes: 4 additions & 0 deletions lib/rspec/matchers/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,10 @@ def supports_block_expectations?
false
end

def supports_value_expectations?
true
end

# Most matchers do not expect call stack jumps.
def expects_call_stack_jump?
false
Expand Down
6 changes: 6 additions & 0 deletions lib/rspec/matchers/matcher_protocol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ class MatcherProtocol
# @return [Boolean] true if this matcher can be used in block expressions.
# @note If not defined, RSpec assumes a value of `false` for this method.

# @!method supports_value_expectations?
# Indicates that this matcher can be used in a value expectation expression,
# such as `expect(foo).to eq(bar)`.
# @return [Boolean] true if this matcher can be used in value expressions.
# @note If not defined, RSpec assumes a value of `true` for this method.

# @!method expects_call_stack_jump?
# Indicates that when this matcher is used in a block expectation
# expression, it expects the block to use a ruby construct that causes
Expand Down
4 changes: 2 additions & 2 deletions spec/rspec/expectations/extensions/kernel_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
RSpec.describe Object, "#should" do
before(:example) do
@target = "target"
@matcher = double("matcher")
@matcher = double("matcher", :supports_value_expectations? => true)
allow(@matcher).to receive(:matches?).and_return(true)
allow(@matcher).to receive(:failure_message)
end
Expand Down Expand Up @@ -59,7 +59,7 @@ def method_missing(name, *args)
RSpec.describe Object, "#should_not" do
before(:example) do
@target = "target"
@matcher = double("matcher")
@matcher = double("matcher", :supports_value_expectations? => true)
end

it "accepts and interacts with a matcher" do
Expand Down
2 changes: 1 addition & 1 deletion spec/rspec/matchers/aliased_matcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def description
end
RSpec::Matchers.alias_matcher :alias_of_my_base_matcher, :my_base_matcher

it_behaves_like "an RSpec matcher", :valid_value => 13, :invalid_value => nil do
it_behaves_like "an RSpec value matcher", :valid_value => 13, :invalid_value => nil do
let(:matcher) { alias_of_my_base_matcher }
end

Expand Down
2 changes: 1 addition & 1 deletion spec/rspec/matchers/built_in/all_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module RSpec::Matchers::BuiltIn
RSpec.describe All do

it_behaves_like 'an RSpec matcher', :valid_value => ['A', 'A', 'A'], :invalid_value => ['A', 'A', 'B'], :disallows_negation => true do
it_behaves_like 'an RSpec value matcher', :valid_value => ['A', 'A', 'A'], :invalid_value => ['A', 'A', 'B'], :disallows_negation => true do
let(:matcher) { all( eq('A') ) }
end

Expand Down
2 changes: 1 addition & 1 deletion spec/rspec/matchers/built_in/be_between_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def inspect
end
end

it_behaves_like "an RSpec matcher", :valid_value => (10), :invalid_value => (11) do
it_behaves_like "an RSpec value matcher", :valid_value => (10), :invalid_value => (11) do
let(:matcher) { be_between(1, 10) }
end

Expand Down
2 changes: 1 addition & 1 deletion spec/rspec/matchers/built_in/be_instance_of_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module RSpec
module Matchers
[:be_an_instance_of, :be_instance_of].each do |method|
RSpec.describe "expect(actual).to #{method}(expected)" do
it_behaves_like "an RSpec matcher", :valid_value => "a", :invalid_value => 5 do
it_behaves_like "an RSpec value matcher", :valid_value => "a", :invalid_value => 5 do
let(:matcher) { send(method, String) }
end

Expand Down
2 changes: 1 addition & 1 deletion spec/rspec/matchers/built_in/be_kind_of_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module RSpec
module Matchers
[:be_a_kind_of, :be_kind_of].each do |method|
RSpec.describe "expect(actual).to #{method}(expected)" do
it_behaves_like "an RSpec matcher", :valid_value => 5, :invalid_value => "a" do
it_behaves_like "an RSpec value matcher", :valid_value => 5, :invalid_value => "a" do
let(:matcher) { send(method, Integer) }
end

Expand Down
2 changes: 1 addition & 1 deletion spec/rspec/matchers/built_in/be_within_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module RSpec
module Matchers
RSpec.describe "expect(actual).to be_within(delta).of(expected)" do
it_behaves_like "an RSpec matcher", :valid_value => 5, :invalid_value => -5 do
it_behaves_like "an RSpec value matcher", :valid_value => 5, :invalid_value => -5 do
let(:matcher) { be_within(2).of(4.0) }
end

Expand Down
Loading