From c0334568c71a99d19f26513293e3f6428db2fd96 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Fri, 18 Dec 2020 22:16:12 +0300 Subject: [PATCH 1/2] Remove monkey patching http://rspec.info/blog/2013/07/the-plan-for-rspec-3/#zero_monkey_patching_mode > we do want to encourage people to switch to the new syntax, so we plan to make RSpec 3 print a warning on first usage of any the old syntax methods (should, should_not, should_receive, etc) unless the should syntax has been explicitly enabled. This should nudge folks towards the new syntax while keeping RSpec friendly to new users and will pave the way for the old syntax to be disabled by default in RSpec 4. > zero-monkey-patching mode for RSpec... We plan for these config options to become the defaults in RSpec 4.0, so that RSpec 4.0 will have zero monkey patching out of the box. As for "disabled by default" vs "completely removed" and "default, out of the box" vs "impossible" I can only say that RSpec 4 was probably planned to be released earlier, as: > we'll probably be dropping support for 1.8.7 in RSpec 4 but we've also dropped 1.9, 2.0, 2.1 and 2.2 https://github.com/rspec/rspec-core/issues/2301#issuecomment-234612382 > In RSpec 4, we plan to extract all monkey patching from RSpec and move it into a separate gem, so that monkey patching is opt-in instead of opt-out and users have to explicitly install and load a gem to get it. `rspec-should` (or `rspec-monkey` as it's also about exposing example group DSL in the top-level/Module?) will be released later. Those using the monkey-patched `should` syntax are not encouraged to update to RSpec 4 until this gem is extracted. Those using the globally-exposed DSL are encouraged to use `RSpec.describe`/`RSpec.shared_examples_for` instead. --- Changelog.md | 1 + README.md | 11 +- Should.md | 176 ------------------ features/.nav | 1 - features/built_in_matchers/README.md | 4 +- features/support/disallow_certain_apis.rb | 33 ---- features/syntax_configuration.feature | 92 --------- lib/rspec/expectations/configuration.rb | 62 ------ lib/rspec/expectations/syntax.rb | 130 ------------- lib/rspec/matchers.rb | 4 +- lib/rspec/matchers/built_in/operators.rb | 6 +- .../kw_argument_delegation_spec.rb | 11 ++ spec/rspec/expectations/configuration_spec.rb | 140 -------------- .../expectations/extensions/kernel_spec.rb | 79 -------- spec/rspec/expectations/syntax_spec.rb | 14 +- spec/rspec/matchers/built_in/be_spec.rb | 8 +- .../matchers/built_in/contain_exactly_spec.rb | 70 ------- .../rspec/matchers/built_in/operators_spec.rb | 140 +++++++------- spec/spec_helper.rb | 51 +---- 19 files changed, 102 insertions(+), 931 deletions(-) delete mode 100644 Should.md delete mode 100644 features/support/disallow_certain_apis.rb delete mode 100644 features/syntax_configuration.feature delete mode 100644 lib/rspec/expectations/syntax.rb create mode 100644 spec/integration/kw_argument_delegation_spec.rb delete mode 100644 spec/rspec/expectations/extensions/kernel_spec.rb diff --git a/Changelog.md b/Changelog.md index a2ee4608b..10a7b736d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ Breaking Changes: * Ruby < 2.3 is no longer supported. (Phil Pirozhkov, #1231) +* Remove `should` and `should_not` syntax (including one-liners). (Phil Pirozhkov, #1245) Enhancements: diff --git a/README.md b/README.md index 3888c9377..e7a4e38bf 100644 --- a/README.md +++ b/README.md @@ -219,16 +219,15 @@ expect( ## `should` syntax In addition to the `expect` syntax, rspec-expectations continues to support the -`should` syntax: +non-monkey patching `should` syntax: ```ruby -actual.should eq expected -actual.should be > 3 -[1, 2, 3].should_not include 4 +subject(:number) { 4 } +it { should eq(4) } +it { should be > 3 } +it { should_not be_odd } ``` -See [detailed information on the `should` syntax and its usage.](https://github.com/rspec/rspec-expectations/blob/main/Should.md) - ## Compound Matcher Expressions You can also create compound matcher expressions using `and` or `or`: diff --git a/Should.md b/Should.md deleted file mode 100644 index ec8d66811..000000000 --- a/Should.md +++ /dev/null @@ -1,176 +0,0 @@ -# `should` and `should_not` syntax - -From the beginning RSpec::Expectations provided `should` and `should_not` methods -to define expectations on any object. In version 2.11 `expect` method was -introduced which is now the recommended way to define expectations on an object. - -### Why switch over from `should` to `expect` - -#### Fix edge case issues - -`should` and `should_not` work by being added to every object. However, RSpec -does not own every object and cannot ensure they work consistently on every object. -In particular, they can lead to surprising failures when used with BasicObject-subclassed -proxy objects. - -`expect` avoids these problems altogether by not needing to be available on all objects. - -#### Unification of block and value syntaxes - -Before version 2.11 `expect` was just a more readable alternative for block -expectations. Since version 2.11 `expect` can be used for both block and value -expectations. - -```ruby -expect(actual).to eq(expected) -expect { ... }.to raise_error(ErrorClass) -``` - -See -[http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax](http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax) -For a detailed explanation - -### One-liners - -The one-liner syntax supported by -[rspec-core](http://rubydoc.info/gems/rspec-core) uses `should` even when -`config.syntax = :expect`. It reads better than the alternative, and does not -require a global monkey patch: - -```ruby -describe User do - it { should validate_presence_of :email } -end -``` - -It can also be expressed with the `is_expected` syntax: - -```ruby -describe User do - it { is_expected.to validate_presence_of :email } -end -``` - -### Using either `expect` or `should` or both - -By default, both `expect` and `should` syntaxes are available. In the future, -the default may be changed to only enable the `expect` syntax. - -If you want your project to only use any one of these syntaxes, you can configure -it: - -```ruby -RSpec.configure do |config| - config.expect_with :rspec do |c| - c.syntax = :expect # disables `should` - # or - c.syntax = :should # disables `expect` - # or - c.syntax = [:should, :expect] # default, enables both `should` and `expect` - end -end -``` - -See -[RSpec::Expectations::Syntax#expect](http://rubydoc.info/gems/rspec-expectations/RSpec/Expectations/Syntax:expect) -for more information. - -## Usage - -The `should` and `should_not` methods can be used to define expectations on any -object. - -```ruby -actual.should eq expected -actual.should be > 3 -[1, 2, 3].should_not include 4 -``` - -## Using Built-in matchers - -### Equivalence - -```ruby -actual.should eq(expected) # passes if actual == expected -actual.should == expected # passes if actual == expected -actual.should_not eql(expected) # passes if actual.eql?(expected) -``` - -Note: we recommend the `eq` matcher over `==` to avoid Ruby's "== in a -useless context" warning when the `==` matcher is used anywhere but the -last statement of an example. - -### Identity - -```ruby -actual.should be(expected) # passes if actual.equal?(expected) -actual.should_not equal(expected) # passes if actual.equal?(expected) -``` - -### Comparisons - -```ruby -actual.should be > expected -actual.should be >= expected -actual.should be <= expected -actual.should be < expected -actual.should be_within(delta).of(expected) -``` - -### Regular expressions - -```ruby -actual.should match(/expression/) -actual.should =~ /expression/ -``` - -### Types/classes - -```ruby -actual.should be_an_instance_of(expected) -actual.should_not be_a_kind_of(expected) -``` - -### Truthiness - -```ruby -actual.should be_true # passes if actual is truthy (not nil or false) -actual.should be_false # passes if actual is falsy (nil or false) -actual.should be_nil # passes if actual is nil -``` - -### Predicate matchers - -```ruby -actual.should be_xxx # passes if actual.xxx? -actual.should_not have_xxx(:arg) # passes if actual.has_xxx?(:arg) -``` - -### Ranges (Ruby >= 1.9 only) - -```ruby -(1..10).should cover(3) -``` - -### Collection membership - -```ruby -actual.should include(expected) -actual.should start_with(expected) -actual.should end_with(expected) -``` - -#### Examples - -```ruby -[1,2,3].should include(1) -[1,2,3].should include(1, 2) -[1,2,3].should start_with(1) -[1,2,3].should start_with(1,2) -[1,2,3].should end_with(3) -[1,2,3].should end_with(2,3) -{:a => 'b'}.should include(:a => 'b') -"this string".should include("is str") -"this string".should start_with("this") -"this string".should end_with("ring") -``` diff --git a/features/.nav b/features/.nav index 21aeb70c8..66eba770b 100644 --- a/features/.nav +++ b/features/.nav @@ -36,7 +36,6 @@ - customized_message.feature - diffing.feature - implicit_docstrings.feature -- syntax_configuration.feature - test_frameworks: - test_unit.feature - Changelog.md diff --git a/features/built_in_matchers/README.md b/features/built_in_matchers/README.md index a9cda832d..5f6684649 100644 --- a/features/built_in_matchers/README.md +++ b/features/built_in_matchers/README.md @@ -1,13 +1,11 @@ rspec-expectations ships with a number of built-in matchers. Each matcher can be used with `expect(..).to` or `expect(..).not_to` to define positive and negative expectations -respectively on an object. Most matchers can also be accessed using the `(...).should` and -`(...).should_not` syntax; see [using should syntax](https://github.com/rspec/rspec-expectations/blob/main/Should.md) for why we recommend using `expect`. +respectively on an object. e.g. expect(result).to eq(3) expect(list).not_to be_empty - pi.should be > 3 ## Object identity diff --git a/features/support/disallow_certain_apis.rb b/features/support/disallow_certain_apis.rb deleted file mode 100644 index 958c0775c..000000000 --- a/features/support/disallow_certain_apis.rb +++ /dev/null @@ -1,33 +0,0 @@ -# This file is designed to prevent the use of certain APIs that -# we don't want used from our cukes, since they function as documentation. - -if defined?(Cucumber) - require 'shellwords' - Before('~@allow-disallowed-api') do - set_environment_variable('SPEC_OPTS', "-r#{Shellwords.escape(__FILE__)}") - end -else - module DisallowOneLinerShould - def should(*) - raise "one-liner should is not allowed" - end - - def should_not(*) - raise "one-liner should_not is not allowed" - end - end - - RSpec.configure do |rspec| - rspec.expose_dsl_globally = false if rspec.respond_to?(:expose_dsl_globally) - - rspec.mock_with :rspec do |mocks| - mocks.syntax = :expect if mocks.respond_to?(:syntax=) # RSpec 4 removed `syntax=` - end - - rspec.expect_with :rspec do |expectations| - expectations.syntax = :expect - end - - rspec.include DisallowOneLinerShould - end -end diff --git a/features/syntax_configuration.feature b/features/syntax_configuration.feature deleted file mode 100644 index d7ef728a8..000000000 --- a/features/syntax_configuration.feature +++ /dev/null @@ -1,92 +0,0 @@ -@allow-disallowed-api -Feature: Syntax Configuration - - The primary syntax provided by rspec-expectations is based on - the `expect` method, which explicitly wraps an object or block - of code in order to set an expectation on it. - - There's also an older `should`-based syntax, which relies upon `should` being - monkey-patched onto every object in the system. However, this syntax can at times lead to - some surprising failures, since RSpec does not own every object in the system and cannot - guarantee that it will always work consistently. - - We recommend you use the `expect` syntax unless you have a specific reason you prefer the - `should` syntax. We have no plans to ever completely remove the `should` syntax but starting - in RSpec 3, a deprecation warning will be issued if you do not explicitly enable it, with the - plan to disable it by default in RSpec 4 (and potentially move it into an external gem). - - If you have an old `should`-based project that you would like to upgrade to the `expect`, - check out [transpec](http://yujinakayama.me/transpec/), which can perform the conversion automatically for you. - - Background: - Given a file named "spec/syntaxes_spec.rb" with: - """ruby - require 'spec_helper' - - RSpec.describe "using the should syntax" do - specify { 3.should eq(3) } - specify { 3.should_not eq(4) } - specify { lambda { raise "boom" }.should raise_error("boom") } - specify { lambda { }.should_not raise_error } - end - - RSpec.describe "using the expect syntax" do - specify { expect(3).to eq(3) } - specify { expect(3).not_to eq(4) } - specify { expect { raise "boom" }.to raise_error("boom") } - specify { expect { }.not_to raise_error } - end - """ - - Scenario: Both syntaxes are available by default - Given a file named "spec/spec_helper.rb" with: - """ruby - """ - When I run `rspec` - Then the examples should all pass - And the output should contain "Using `should` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax is deprecated" - - Scenario: Disable should syntax - Given a file named "spec/spec_helper.rb" with: - """ruby - RSpec.configure do |config| - config.expect_with :rspec do |expectations| - expectations.syntax = :expect - end - end - """ - When I run `rspec` - Then the output should contain all of these: - | 8 examples, 4 failures | - | undefined method `should' | - - Scenario: Disable expect syntax - Given a file named "spec/spec_helper.rb" with: - """ruby - RSpec.configure do |config| - config.expect_with :rspec do |expectations| - expectations.syntax = :should - end - config.mock_with :rspec do |mocks| - mocks.syntax = :should if mocks.respond_to?(:syntax=) # RSpec 4 removed `syntax=` - end - end - """ - When I run `rspec` - Then the output should contain all of these: - | 8 examples, 4 failures | - | undefined method `expect' | - - Scenario: Explicitly enable both syntaxes - Given a file named "spec/spec_helper.rb" with: - """ruby - RSpec.configure do |config| - config.expect_with :rspec do |expectations| - expectations.syntax = [:should, :expect] - end - end - """ - When I run `rspec` - Then the examples should all pass - And the output should not contain "deprecated" - diff --git a/lib/rspec/expectations/configuration.rb b/lib/rspec/expectations/configuration.rb index a16b5b5e4..ddfbb5313 100644 --- a/lib/rspec/expectations/configuration.rb +++ b/lib/rspec/expectations/configuration.rb @@ -1,5 +1,3 @@ -RSpec::Support.require_rspec_expectations "syntax" - module RSpec module Expectations # Provides configuration options for rspec-expectations. @@ -31,32 +29,6 @@ def initialize @strict_predicate_matchers = false end - # Configures the supported syntax. - # @param [Array, Symbol] values the syntaxes to enable - # @example - # RSpec.configure do |rspec| - # rspec.expect_with :rspec do |c| - # c.syntax = :should - # # or - # c.syntax = :expect - # # or - # c.syntax = [:should, :expect] - # end - # end - def syntax=(values) - if Array(values).include?(:expect) - Expectations::Syntax.enable_expect - else - Expectations::Syntax.disable_expect - end - - if Array(values).include?(:should) - Expectations::Syntax.enable_should - else - Expectations::Syntax.disable_should - end - end - # Configures the maximum character length that RSpec will print while # formatting an object. You can set length to nil to prevent RSpec from # doing truncation. @@ -71,19 +43,6 @@ def max_formatted_output_length=(length) RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = length end - # The list of configured syntaxes. - # @return [Array] the list of configured syntaxes. - # @example - # unless RSpec::Matchers.configuration.syntax.include?(:expect) - # raise "this RSpec extension gem requires the rspec-expectations `:expect` syntax" - # end - def syntax - syntaxes = [] - syntaxes << :should if Expectations::Syntax.should_enabled? - syntaxes << :expect if Expectations::Syntax.expect_enabled? - syntaxes - end - if ::RSpec.respond_to?(:configuration) def color? ::RSpec.configuration.color_enabled? @@ -102,18 +61,6 @@ def color? end end - # Adds `should` and `should_not` to the given classes - # or modules. This can be used to ensure `should` works - # properly on things like proxy objects. - # - # @param [Array] modules the list of classes or modules - # to add `should` and `should_not` to. - def add_should_and_should_not_to(*modules) - modules.each do |mod| - Expectations::Syntax.enable_should(mod) - end - end - # Sets or gets the backtrace formatter. The backtrace formatter should # implement `#format_backtrace(Array)`. This is used # to format backtraces of errors handled by the `raise_error` @@ -145,12 +92,6 @@ def include_chain_clauses_in_custom_matcher_descriptions? @include_chain_clauses_in_custom_matcher_descriptions ||= false end - # @private - def reset_syntaxes_to_default - self.syntax = [:should, :expect] - RSpec::Expectations::Syntax.warn_about_should! - end - # @api private # Null implementation of a backtrace formatter used by default # when rspec-core is not loaded. Does no filtering. @@ -222,8 +163,5 @@ def false_positives_handler def self.configuration @configuration ||= Configuration.new end - - # set default syntax - configuration.reset_syntaxes_to_default end end diff --git a/lib/rspec/expectations/syntax.rb b/lib/rspec/expectations/syntax.rb deleted file mode 100644 index c61670cd4..000000000 --- a/lib/rspec/expectations/syntax.rb +++ /dev/null @@ -1,130 +0,0 @@ -module RSpec - module Expectations - # @api private - # Provides methods for enabling and disabling the available - # syntaxes provided by rspec-expectations. - module Syntax - module_function - - # @api private - # Determines where we add `should` and `should_not`. - def default_should_host - @default_should_host ||= ::Object.ancestors.last - end - - # @api private - # Instructs rspec-expectations to warn on first usage of `should` or `should_not`. - # Enabled by default. This is largely here to facilitate testing. - def warn_about_should! - @warn_about_should = true - end - - # @api private - # Generates a deprecation warning for the given method if no warning - # has already been issued. - def warn_about_should_unless_configured(method_name) - return unless @warn_about_should - - RSpec.deprecate( - "Using `#{method_name}` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax", - :replacement => "the new `:expect` syntax or explicitly enable `:should` with `config.expect_with(:rspec) { |c| c.syntax = :should }`" - ) - - @warn_about_should = false - end - - # @api private - # Enables the `should` syntax. - def enable_should(syntax_host=default_should_host) - @warn_about_should = false if syntax_host == default_should_host - return if should_enabled?(syntax_host) - - syntax_host.module_exec do - def should(matcher=nil, message=nil, &block) - ::RSpec::Expectations::Syntax.warn_about_should_unless_configured(::Kernel.__method__) - ::RSpec::Expectations::PositiveExpectationHandler.handle_matcher(self, matcher, message, &block) - end - - def should_not(matcher=nil, message=nil, &block) - ::RSpec::Expectations::Syntax.warn_about_should_unless_configured(::Kernel.__method__) - ::RSpec::Expectations::NegativeExpectationHandler.handle_matcher(self, matcher, message, &block) - end - end - end - - # @api private - # Disables the `should` syntax. - def disable_should(syntax_host=default_should_host) - return unless should_enabled?(syntax_host) - - syntax_host.module_exec do - undef should - undef should_not - end - end - - # @api private - # Enables the `expect` syntax. - def enable_expect(syntax_host=::RSpec::Matchers) - return if expect_enabled?(syntax_host) - - syntax_host.module_exec do - def expect(value=::RSpec::Expectations::ExpectationTarget::UndefinedValue, &block) - ::RSpec::Expectations::ExpectationTarget.for(value, block) - end - end - end - - # @api private - # Disables the `expect` syntax. - def disable_expect(syntax_host=::RSpec::Matchers) - return unless expect_enabled?(syntax_host) - - syntax_host.module_exec do - undef expect - end - end - - # @api private - # Indicates whether or not the `should` syntax is enabled. - def should_enabled?(syntax_host=default_should_host) - syntax_host.method_defined?(:should) - end - - # @api private - # Indicates whether or not the `expect` syntax is enabled. - def expect_enabled?(syntax_host=::RSpec::Matchers) - syntax_host.method_defined?(:expect) - end - end - end -end - -# The legacy `:should` syntax adds the following methods directly to -# `BasicObject` so that they are available off of any object. Note, however, -# that this syntax does not always play nice with delegate/proxy objects. -# We recommend you use the non-monkeypatching `:expect` syntax instead. -class BasicObject - # @method should(matcher, message) - # Passes if `matcher` returns true. Available on every `Object`. - # @example - # actual.should eq expected - # actual.should match /expression/ - # @param [Matcher] - # matcher - # @param [String] message optional message to display when the expectation fails - # @return [Boolean] true if the expectation succeeds (else raises) - # @note This is only available when you have enabled the `:should` syntax. - # @see RSpec::Matchers - - # @method should_not(matcher, message) - # Passes if `matcher` returns false. Available on every `Object`. - # @example - # actual.should_not eq expected - # @param [Matcher] - # matcher - # @param [String] message optional message to display when the expectation fails - # @return [Boolean] false if the negative expectation succeeds (else raises) - # @note This is only available when you have enabled the `:should` syntax. - # @see RSpec::Matchers -end diff --git a/lib/rspec/matchers.rb b/lib/rspec/matchers.rb index dc978411a..e25cdac62 100644 --- a/lib/rspec/matchers.rb +++ b/lib/rspec/matchers.rb @@ -260,7 +260,6 @@ def self.alias_matcher(*args, &block) # @!method self.define_negated_matcher(negated_name, base_name, &description_override) # Extended from {RSpec::Matchers::DSL#define_negated_matcher}. - # @method expect # Supports `expect(actual).to matcher` syntax by wrapping `actual` in an # `ExpectationTarget`. # @example @@ -269,6 +268,9 @@ def self.alias_matcher(*args, &block) # @return [Expectations::ExpectationTarget] # @see Expectations::ExpectationTarget#to # @see Expectations::ExpectationTarget#not_to + def expect(value=::RSpec::Expectations::ExpectationTarget::UndefinedValue, &block) + ::RSpec::Expectations::ExpectationTarget.for(value, block) + end # Allows multiple expectations in the provided block to fail, and then # aggregates them into a single exception, rather than aborting on the diff --git a/lib/rspec/matchers/built_in/operators.rb b/lib/rspec/matchers/built_in/operators.rb index 64f8f3b23..188eb14a9 100644 --- a/lib/rspec/matchers/built_in/operators.rb +++ b/lib/rspec/matchers/built_in/operators.rb @@ -6,7 +6,7 @@ module BuiltIn # @api private # Provides the implementation for operator matchers. # Not intended to be instantiated directly. - # Only available for use with `should`. + # Only available for use with `should` one-liner syntax. class OperatorMatcher class << self # @private @@ -93,7 +93,7 @@ def eval_match(actual, operator, expected) end # @private - # Handles operator matcher for `should`. + # Handles operator matcher for positive expectations class PositiveOperatorMatcher < OperatorMatcher def __delegate_operator(actual, operator, expected) if actual.__send__(operator, expected) @@ -112,7 +112,7 @@ def __delegate_operator(actual, operator, expected) end # @private - # Handles operator matcher for `should_not`. + # Handles operator matcher for negative expectations class NegativeOperatorMatcher < OperatorMatcher def __delegate_operator(actual, operator, expected) return false unless actual.__send__(operator, expected) diff --git a/spec/integration/kw_argument_delegation_spec.rb b/spec/integration/kw_argument_delegation_spec.rb new file mode 100644 index 000000000..a9204ecfb --- /dev/null +++ b/spec/integration/kw_argument_delegation_spec.rb @@ -0,0 +1,11 @@ +RSpec.describe "keyword argument delegation" do + # We erroneously introduced keyword argument helpers to try to remove + # the warnings generated with our normal delegation, these can cause + # the arguments to be cast as hash and need to not be + + it "does not coerce to hash when passed through an equal? expectation" do + cls = Class.new { def to_hash; {a:1}; end } + obj = cls.new + expect(obj).to be_equal(obj) + end +end diff --git a/spec/rspec/expectations/configuration_spec.rb b/spec/rspec/expectations/configuration_spec.rb index a488068f4..59ced1c05 100644 --- a/spec/rspec/expectations/configuration_spec.rb +++ b/spec/rspec/expectations/configuration_spec.rb @@ -121,146 +121,6 @@ class << rspec_dup; undef configuration; end expect(config.on_potential_false_positives).to eq :raise end end - - shared_examples "configuring the expectation syntax" do - before do - @orig_syntax = RSpec::Matchers.configuration.syntax - end - - after do - configure_syntax(@orig_syntax) - end - - it 'can limit the syntax to :should' do - configure_syntax :should - configured_syntax.should eq([:should]) - - 3.should eq(3) - 3.should_not eq(4) - lambda { expect(6).to eq(6) }.should raise_error(NameError) - end - - it 'is a no-op when configured to :should twice' do - configure_syntax :should - method_added_count = 0 - allow(Expectations::Syntax.default_should_host).to receive(:method_added) { method_added_count += 1 } - configure_syntax :should - - method_added_count.should eq(0) - end - - it 'can limit the syntax to :expect' do - configure_syntax :expect - expect(configured_syntax).to eq([:expect]) - - expect(3).to eq(3) - expect { 3.should eq(3) }.to raise_error(NameError) - expect { 3.should_not eq(3) }.to raise_error(NameError) - end - - it 'is a no-op when configured to :expect twice' do - allow(RSpec::Matchers).to receive(:method_added).and_raise("no methods should be added here") - - configure_syntax :expect - configure_syntax :expect - end - - describe "`:should` being enabled by default deprecation" do - before { configure_default_syntax } - - it "warns when the should syntax is called by default" do - expected_arguments = [ - /Using.*without explicitly enabling/, - { :replacement => "the new `:expect` syntax or explicitly enable `:should` with `config.expect_with(:rspec) { |c| c.syntax = :should }`" } - ] - - expect(RSpec).to receive(:deprecate).with(*expected_arguments) - 3.should eq(3) - end - - it "includes the call site in the deprecation warning by default" do - expect_deprecation_with_call_site(__FILE__, __LINE__ + 1) - 3.should eq(3) - end - - it "does not warn when only the should syntax is explicitly configured" do - configure_syntax(:should) - RSpec.should_not receive(:deprecate) - 3.should eq(3) - end - - it "does not warn when both the should and expect syntaxes are explicitly configured" do - configure_syntax([:should, :expect]) - expect(RSpec).not_to receive(:deprecate) - 3.should eq(3) - end - end - - it 'can re-enable the :should syntax' do - configure_syntax :expect - configure_syntax [:should, :expect] - configured_syntax.should eq([:should, :expect]) - - 3.should eq(3) - 3.should_not eq(4) - expect(3).to eq(3) - end - - it 'can re-enable the :expect syntax' do - configure_syntax :should - configure_syntax [:should, :expect] - configured_syntax.should eq([:should, :expect]) - - 3.should eq(3) - 3.should_not eq(4) - expect(3).to eq(3) - end - end - - def configure_default_syntax - RSpec::Matchers.configuration.reset_syntaxes_to_default - end - - describe "configuring rspec-expectations directly" do - it_behaves_like "configuring the expectation syntax" do - def configure_syntax(syntax) - RSpec::Matchers.configuration.syntax = syntax - end - - def configured_syntax - RSpec::Matchers.configuration.syntax - end - end - end - - describe "configuring using the rspec-core config API" do - it_behaves_like "configuring the expectation syntax" do - def configure_syntax(syntax) - RSpec.configure do |rspec| - rspec.expect_with :rspec do |c| - c.syntax = syntax - end - end - end - - def configured_syntax - RSpec.configure do |rspec| - rspec.expect_with :rspec do |c| - return c.syntax - end - end - end - end - end - - it 'enables both syntaxes by default' do - # This is kinda a hack, but since we want to enforce use of - # the expect syntax within our specs here, we have modified the - # config setting, which makes it hard to get at the original - # default value. in spec_helper.rb we store the default value - # in $default_expectation_syntax so we can use it here. - expect($default_expectation_syntax).to contain_exactly(:expect, :should) # rubocop:disable Style/GlobalVars - end end end end diff --git a/spec/rspec/expectations/extensions/kernel_spec.rb b/spec/rspec/expectations/extensions/kernel_spec.rb deleted file mode 100644 index c8c4e20b5..000000000 --- a/spec/rspec/expectations/extensions/kernel_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -RSpec.describe Object, "#should" do - before(:example) do - @target = "target" - @matcher = double("matcher") - allow(@matcher).to receive(:matches?).and_return(true) - allow(@matcher).to receive(:failure_message) - end - - it "accepts and interacts with a matcher" do - expect(@matcher).to receive(:matches?).with(@target).and_return(true) - expect(@target).to @matcher - end - - it "asks for a failure_message when matches? returns false" do - expect(@matcher).to receive(:matches?).with(@target).and_return(false) - expect(@matcher).to receive(:failure_message).and_return("the failure message") - expect { - expect(@target).to @matcher - }.to fail_with("the failure message") - end - - context "on interpretters that have BasicObject" do - let(:proxy_class) do - Class.new(BasicObject) do - def initialize(target) - @target = target - end - - def proxied? - true - end - - def respond_to?(method, *args) - method.to_sym == :proxied? || @target.respond_to?(symbol, *args) - end - - def method_missing(name, *args) - @target.send(name, *args) - end - end - end - - it 'works properly on BasicObject-subclassed proxy objects' do - expect(proxy_class.new(Object.new)).to be_proxied - end - - it 'does not break the deprecation check on BasicObject-subclassed proxy objects' do - begin - should_enabled = RSpec::Expectations::Syntax.should_enabled? - RSpec::Expectations::Syntax.enable_should unless should_enabled - proxy_class.new(BasicObject.new).should be_proxied - ensure - RSpec::Expectations::Syntax.disable_should if should_enabled - end - end - end -end - -RSpec.describe Object, "#should_not" do - before(:example) do - @target = "target" - @matcher = double("matcher") - end - - it "accepts and interacts with a matcher" do - expect(@matcher).to receive(:matches?).with(@target).and_return(false) - allow(@matcher).to receive(:failure_message_when_negated) - - expect(@target).not_to @matcher - end - - it "asks for a failure_message_when_negated when matches? returns true" do - expect(@matcher).to receive(:matches?).with(@target).and_return(true) - expect(@matcher).to receive(:failure_message_when_negated).and_return("the failure message for should not") - expect { - expect(@target).not_to @matcher - }.to fail_with("the failure message for should not") - end -end diff --git a/spec/rspec/expectations/syntax_spec.rb b/spec/rspec/expectations/syntax_spec.rb index a931aee4f..19cd87494 100644 --- a/spec/rspec/expectations/syntax_spec.rb +++ b/spec/rspec/expectations/syntax_spec.rb @@ -1,6 +1,6 @@ module RSpec module Expectations - RSpec.describe Syntax do + RSpec.describe 'Syntax' do context "when passing a message to an expectation" do let(:warner) { ::Kernel } @@ -70,18 +70,6 @@ module Expectations end end end - - describe "enabling the should syntax on something other than the default syntax host" do - include_context "with the default expectation syntax" - - it "continues to warn about the should syntax" do - my_host = Class.new - expect(RSpec).to receive(:deprecate) - Syntax.enable_should(my_host) - - 3.should eq 3 - end - end end end end diff --git a/spec/rspec/matchers/built_in/be_spec.rb b/spec/rspec/matchers/built_in/be_spec.rb index 7ef0d9762..fd98aff07 100644 --- a/spec/rspec/matchers/built_in/be_spec.rb +++ b/spec/rspec/matchers/built_in/be_spec.rb @@ -746,14 +746,16 @@ def send end end -RSpec.describe "should be =~", :uses_should do +RSpec.describe "should be =~" do + subject { "a string" } + it "passes when =~ operator returns true" do - "a string".should be =~ /str/ + should be =~ /str/ end it "fails when =~ operator returns false" do expect { - "a string".should be =~ /blah/ + should be =~ /blah/ }.to fail_with(%(expected: =~ /blah/\n got: "a string")) end end diff --git a/spec/rspec/matchers/built_in/contain_exactly_spec.rb b/spec/rspec/matchers/built_in/contain_exactly_spec.rb index 075d7469c..cee758c73 100644 --- a/spec/rspec/matchers/built_in/contain_exactly_spec.rb +++ b/spec/rspec/matchers/built_in/contain_exactly_spec.rb @@ -24,76 +24,6 @@ def to_ary end end -RSpec.describe "should =~ array", :uses_should do - it "passes a valid positive expectation" do - [1, 2].should =~ [2, 1] - end - - it "fails an invalid positive expectation" do - expect { - [1, 2, 3].should =~ [2, 1] - }.to fail_with(/expected collection contained/) - end - - context "when the array defines a `=~` method" do - it 'delegates to that method rather than using the contain_exactly matcher' do - array = [] - def array.=~(other) - other == :foo - end - - array.should =~ :foo - expect { - array.should =~ :bar - }.to fail_with(/expected: :bar/) - end - end - - context 'when the array defines a `send` method' do - it 'still works' do - array = [1, 2] - def array.send; :sent; end - - array.should =~ array - end - end - - context "when the array undefines `=~`" do - it 'still works' do - array_klass = Class.new(Array) { undef =~ } - array = array_klass.new([1, 2]) - - array.should =~ [1, 2] - - expect { - array.should =~ [0, 1, 2] - }.to fail_with(/expected collection contained/) - end - end -end - -RSpec.describe "should_not =~ [:with, :multiple, :args]", :uses_should do - it "fails when the arrays match" do - expect { - [1, 2, 3].should_not =~ [1, 2, 3] - }.to fail_with "expected [1, 2, 3] not to contain exactly 1, 2, and 3" - end - - it "fails when the arrays match in a different order" do - expect { - [1, 3, 2].should_not =~ [1, 2, 3] - }.to fail_with "expected [1, 3, 2] not to contain exactly 1, 2, and 3" - end - - it "passes when there are extra elements in the array" do - [1, 3].should_not =~ [1, 2, 3] - end - - it "passes when there are elements missing from the array" do - [1, 2, 3, 4].should_not =~ [1, 2, 3] - end -end - RSpec.describe "using contain_exactly with expect" do it "passes a valid positive expectation" do expect([1, 2]).to contain_exactly(2, 1) diff --git a/spec/rspec/matchers/built_in/operators_spec.rb b/spec/rspec/matchers/built_in/operators_spec.rb index 830ff38d8..a24ab290b 100644 --- a/spec/rspec/matchers/built_in/operators_spec.rb +++ b/spec/rspec/matchers/built_in/operators_spec.rb @@ -12,195 +12,199 @@ def method_missing(name, *args, &block) end end -RSpec.describe "operator matchers", :uses_should do +RSpec.describe "operator matchers" do describe "should ==" do + subject { 'apple'.dup } + it "delegates message to target" do - subject = "apple".dup expect(subject).to receive(:==).with("apple").and_return(true) - subject.should == "apple" + should == "apple" end it "returns true on success" do - subject = "apple" - (subject.should == "apple").should be_truthy + expect(should == "apple").to be(true) end it "fails when target.==(actual) returns false" do - subject = "apple" expect(RSpec::Expectations).to receive(:fail_with).with(%{expected: "orange"\n got: "apple" (using ==)}, "orange", "apple") - subject.should == "orange" + should == "orange" end - it "works when #method is overriden" do - myobj = MethodOverrideObject.new - expect { - myobj.should == myobj - }.to_not raise_error + context "when #method is overriden" do + subject(:myobj) { MethodOverrideObject.new } + it { expect { should == myobj }.to_not raise_error } end - it "works when implemented via method_missing" do - obj = Object.new + context "when implemented via method_missing" do + let(:obj) { Object.new } + subject(:myobj) { MethodMissingObject.new(obj) } - myobj = MethodMissingObject.new(obj) - (myobj.should == obj).nil? # just to avoid `useless use of == in void context` warning - myobj.should_not == Object.new + it { should == obj } + it { should_not == Object.new } end end describe "unsupported operators" do + subject { "apple" } + it "raises an appropriate error for should != expected" do expect { - "apple".should != "pear" + should != "pear" }.to raise_error(/does not support `should != expected`. Use `should_not == expected`/) end it "raises an appropriate error for should_not != expected" do expect { - "apple".should_not != "pear" + should_not != "pear" }.to raise_error(/does not support `should_not != expected`. Use `should == expected`/) end it "raises an appropriate error for should !~ expected" do expect { - "apple".should !~ /regex/ + should !~ /regex/ }.to raise_error(/does not support `should !~ expected`. Use `should_not =~ expected`/) end it "raises an appropriate error for should_not !~ expected" do expect { - "apple".should_not !~ /regex/ + should_not !~ /regex/ }.to raise_error(/does not support `should_not !~ expected`. Use `should =~ expected`/) end end describe "should_not ==" do + subject { "orange" } + it "delegates message to target" do - subject = "orange".dup expect(subject).to receive(:==).with("apple").and_return(false) - subject.should_not == "apple" + should_not == "apple" end - it "returns true on success" do - subject = "apple" - (subject.should_not == "orange").should be_falsey + it "returns false on success" do + expect(should_not == "apple").to be(false) end - it "fails when target.==(actual) returns false" do - subject = "apple" - expect(RSpec::Expectations).to receive(:fail_with).with(%(expected not: == "apple"\n got: "apple"), "apple", "apple") - subject.should_not == "apple" + it "fails when target.==(actual) returns true" do + expect(RSpec::Expectations).to receive(:fail_with).with(%(expected not: == "orange"\n got: "orange"), "orange", "orange") + should_not == "orange" end end describe "should ===" do + subject { "apple" } + it "delegates message to target" do - subject = "apple".dup expect(subject).to receive(:===).with("apple").and_return(true) - subject.should === "apple" + should === "apple" end it "fails when target.===(actual) returns false" do - subject = "apple".dup expect(subject).to receive(:===).with("orange").and_return(false) expect(RSpec::Expectations).to receive(:fail_with).with(%{expected: "orange"\n got: "apple" (using ===)}, "orange", "apple") - subject.should === "orange" + should === "orange" end end describe "should_not ===" do + subject { "apple" } + it "delegates message to target" do - subject = "orange".dup expect(subject).to receive(:===).with("apple").and_return(false) - subject.should_not === "apple" + should_not === "apple" end - it "fails when target.===(actual) returns false" do - subject = "apple".dup + it "fails when target.===(actual) returns true" do expect(subject).to receive(:===).with("apple").and_return(true) expect(RSpec::Expectations).to receive(:fail_with).with(%(expected not: === "apple"\n got: "apple"), "apple", "apple") - subject.should_not === "apple" + should_not === "apple" end end describe "should =~" do + subject { "foo" } + it "delegates message to target" do - subject = "foo".dup expect(subject).to receive(:=~).with(/oo/).and_return(true) - subject.should =~ /oo/ + should =~ /oo/ end it "fails when target.=~(actual) returns false" do - subject = "fu".dup - expect(subject).to receive(:=~).with(/oo/).and_return(false) - expect(RSpec::Expectations).to receive(:fail_with).with(%{expected: /oo/\n got: "fu" (using =~)}, /oo/, "fu") - subject.should =~ /oo/ + expect(RSpec::Expectations).to receive(:fail_with).with(%{expected: /fu/\n got: "foo" (using =~)}, /fu/, "foo") + should =~ /fu/ end end describe "should_not =~" do + subject { "foo" } + it "delegates message to target" do - subject = "fu".dup - expect(subject).to receive(:=~).with(/oo/).and_return(false) - subject.should_not =~ /oo/ + expect(subject).to receive(:=~).with(/fu/).and_return(false) + should_not =~ /fu/ end it "fails when target.=~(actual) returns false" do - subject = "foo".dup - expect(subject).to receive(:=~).with(/oo/).and_return(true) expect(RSpec::Expectations).to receive(:fail_with).with(%(expected not: =~ /oo/\n got: "foo"), /oo/, "foo") - subject.should_not =~ /oo/ + should_not =~ /oo/ end end describe "should >" do + subject { 4 } + it "passes if > passes" do - 4.should > 3 + should > 3 end it "fails if > fails" do expect(RSpec::Expectations).to receive(:fail_with).with("expected: > 5\n got: 4", 5, 4) - 4.should > 5 + should > 5 end end describe "should >=" do + subject { 4 } + it "passes if actual == expected" do - 4.should >= 4 + should >= 4 end it "passes if actual > expected" do - 4.should >= 3 + should >= 3 end it "fails if > fails" do expect(RSpec::Expectations).to receive(:fail_with).with("expected: >= 5\n got: 4", 5, 4) - 4.should >= 5 + should >= 5 end end describe "should <" do + subject { 4 } + it "passes if < passes" do - 4.should < 5 + should < 5 end it "fails if > fails" do expect(RSpec::Expectations).to receive(:fail_with).with("expected: < 3\n got: 4", 3, 4) - 4.should < 3 + should < 3 end end describe "should <=" do + subject { 4 } + it "passes if actual == expected" do - 4.should <= 4 + should <= 4 end it "passes if actual < expected" do - 4.should <= 5 + should <= 5 end it "fails if > fails" do expect(RSpec::Expectations).to receive(:fail_with).with("expected: <= 3\n got: 4", 3, 4) - 4.should <= 3 + should <= 3 end end @@ -228,22 +232,20 @@ def method_missing(name, *args, &block) end describe RSpec::Matchers::BuiltIn::PositiveOperatorMatcher do + subject(:o) { Object.new } + it "works when the target has implemented #send" do - o = Object.new def o.send(*_args); raise "DOH! Library developers shouldn't use #send!" end - expect { - o.should == o - }.not_to raise_error + expect { should == o }.not_to raise_error end end describe RSpec::Matchers::BuiltIn::NegativeOperatorMatcher do + subject(:o) { Object.new } + it "works when the target has implemented #send" do - o = Object.new def o.send(*_args); raise "DOH! Library developers shouldn't use #send!" end - expect { - o.should_not == :foo - }.not_to raise_error + expect { should_not == :foo }.not_to raise_error end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 66498271e..2a64f429d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -39,8 +39,6 @@ def hash_inspect(hash) config.include RSpec::Support::InSubProcess config.expect_with :rspec do |expectations| - $default_expectation_syntax = expectations.syntax # rubocop:disable Style/GlobalVars - expectations.syntax = :expect expectations.include_chain_clauses_in_custom_matcher_descriptions = true expectations.strict_predicate_matchers = true end @@ -49,9 +47,6 @@ def hash_inspect(hash) mocks.verify_partial_doubles = true end - config.shared_context_metadata_behavior = :apply_to_host_groups if config.respond_to?(:shared_context_metadata_behavior=) # RSpec 4 dropped this setting - config.disable_monkey_patching! if config.respond_to?(:disable_monkey_patching!) # RSpec 4 dropped this method - # We don't want rspec-core to look in our `lib` for failure snippets. # When it does that, it inevitably finds this line: # `RSpec::Support.notify_failure(RSpec::Expectations::ExpectationNotMetError.new message)` @@ -60,45 +55,6 @@ def hash_inspect(hash) config.project_source_dirs -= ["lib"] end -RSpec.shared_context "with #should enabled" do - orig_syntax = nil - - before(:all) do - orig_syntax = RSpec::Matchers.configuration.syntax - RSpec::Matchers.configuration.syntax = [:expect, :should] - end - - after(:context) do - RSpec::Matchers.configuration.syntax = orig_syntax - end -end - -RSpec.shared_context "with the default expectation syntax" do - orig_syntax = nil - - before(:context) do - orig_syntax = RSpec::Matchers.configuration.syntax - RSpec::Matchers.configuration.reset_syntaxes_to_default - end - - after(:context) do - RSpec::Matchers.configuration.syntax = orig_syntax - end -end - -RSpec.shared_context "with #should exclusively enabled" do - orig_syntax = nil - - before(:context) do - orig_syntax = RSpec::Matchers.configuration.syntax - RSpec::Matchers.configuration.syntax = :should - end - - after(:context) do - RSpec::Matchers.configuration.syntax = orig_syntax - end -end - RSpec.shared_context "isolate include_chain_clauses_in_custom_matcher_descriptions" do around do |ex| orig = RSpec::Expectations.configuration.include_chain_clauses_in_custom_matcher_descriptions? @@ -112,12 +68,7 @@ def hash_inspect(hash) after(:context) { RSpec::Expectations.configuration.warn_about_potential_false_positives = original_value } end - -RSpec.configure do |config| - config.include_context "with #should enabled", :uses_should - config.include_context "with #should exclusively enabled", :uses_only_should - config.include_context "with warn_about_potential_false_positives set to false", :warn_about_potential_false_positives -end +RSpec.configuration.include_context "with warn_about_potential_false_positives set to false", :warn_about_potential_false_positives module MinitestIntegration include ::RSpec::Support::InSubProcess From e8e8bfc54fd83f9956a2737aea0747b7cf54c064 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Wed, 23 Dec 2020 19:49:07 +0300 Subject: [PATCH 2/2] Remove non-monkey-patching should syntax, too --- README.md | 11 - features/.nav | 1 - features/built_in_matchers/predicates.feature | 24 +- .../custom_matchers/define_matcher.feature | 6 +- features/customized_message.feature | 3 +- lib/rspec/expectations/handler.rb | 4 - lib/rspec/matchers.rb | 3 - lib/rspec/matchers/built_in.rb | 3 - lib/rspec/matchers/built_in/operators.rb | 128 --------- lib/rspec/matchers/dsl.rb | 2 +- .../expectations/expectation_target_spec.rb | 4 +- spec/rspec/matchers/built_in/be_spec.rb | 14 - .../rspec/matchers/built_in/operators_spec.rb | 251 ------------------ 13 files changed, 19 insertions(+), 435 deletions(-) delete mode 100644 lib/rspec/matchers/built_in/operators.rb delete mode 100644 spec/rspec/matchers/built_in/operators_spec.rb diff --git a/README.md b/README.md index e7a4e38bf..03c3ffcae 100644 --- a/README.md +++ b/README.md @@ -216,17 +216,6 @@ expect( ).to match([a_hash_including(:a => 'hash'), a_hash_including(:a => 'another')]) ``` -## `should` syntax - -In addition to the `expect` syntax, rspec-expectations continues to support the -non-monkey patching `should` syntax: - -```ruby -subject(:number) { 4 } -it { should eq(4) } -it { should be > 3 } -it { should_not be_odd } -``` ## Compound Matcher Expressions diff --git a/features/.nav b/features/.nav index 66eba770b..e14a50cbb 100644 --- a/features/.nav +++ b/features/.nav @@ -16,7 +16,6 @@ - have_attributes.feature - include.feature - match.feature - - operators.feature - raise_error.feature - respond_to.feature - satisfy.feature diff --git a/features/built_in_matchers/predicates.feature b/features/built_in_matchers/predicates.feature index 0e0256a6c..41d78abe1 100644 --- a/features/built_in_matchers/predicates.feature +++ b/features/built_in_matchers/predicates.feature @@ -47,8 +47,8 @@ Feature: Predicate matchers Any arguments passed to the matcher will be passed on to the predicate method. - Scenario: should be_zero (based on Integer#zero?) - Given a file named "should_be_zero_spec.rb" with: + Scenario: is_expected.to be_zero (based on Integer#zero?) + Given a file named "be_zero_spec.rb" with: """ruby RSpec.describe 0 do it { is_expected.to be_zero } @@ -58,12 +58,12 @@ Feature: Predicate matchers it { is_expected.to be_zero } # deliberate failure end """ - When I run `rspec should_be_zero_spec.rb` + When I run `rspec be_zero_spec.rb` Then the output should contain "2 examples, 1 failure" And the output should contain "expected `7.zero?` to be truthy, got false" - Scenario: should_not be_empty (based on Array#empty?) - Given a file named "should_not_be_empty_spec.rb" with: + Scenario: is_expected.not_to be_empty (based on Array#empty?) + Given a file named "not_to_be_empty_spec.rb" with: """ruby RSpec.describe [1, 2, 3] do it { is_expected.not_to be_empty } @@ -73,12 +73,12 @@ Feature: Predicate matchers it { is_expected.not_to be_empty } # deliberate failure end """ - When I run `rspec should_not_be_empty_spec.rb` + When I run `rspec not_to_be_empty_spec.rb` Then the output should contain "2 examples, 1 failure" And the output should contain "expected `[].empty?` to be falsey, got true" - Scenario: should have_key (based on Hash#has_key?) - Given a file named "should_have_key_spec.rb" with: + Scenario: is_expected.to have_key (based on Hash#has_key?) + Given a file named "have_key_spec.rb" with: """ruby RSpec.describe Hash do subject { { :foo => 7 } } @@ -86,12 +86,12 @@ Feature: Predicate matchers it { is_expected.to have_key(:bar) } # deliberate failure end """ - When I run `rspec should_have_key_spec.rb` + When I run `rspec have_key_spec.rb` Then the output should contain "2 examples, 1 failure" And the output should contain "expected `{:foo=>7}.has_key?(:bar)` to be truthy, got false" - Scenario: should_not have_all_string_keys (based on custom #has_all_string_keys? method) - Given a file named "should_not_have_all_string_keys_spec.rb" with: + Scenario: is_expected.to have_decimals (based on custom #have_decimals? method) + Given a file named "have_decimals_spec.rb" with: """ruby class Float def has_decimals? @@ -112,7 +112,7 @@ Feature: Predicate matchers end end """ - When I run `rspec should_not_have_all_string_keys_spec.rb` + When I run `rspec have_decimals_spec.rb` Then the output should contain "2 examples, 1 failure" And the output should contain "expected `42.0.has_decimals?` to be truthy, got false" diff --git a/features/custom_matchers/define_matcher.feature b/features/custom_matchers/define_matcher.feature index 52f9252d0..73ce7ee44 100644 --- a/features/custom_matchers/define_matcher.feature +++ b/features/custom_matchers/define_matcher.feature @@ -203,7 +203,7 @@ Feature: Define a custom matcher end RSpec.describe "these two arrays" do - specify "should be similar" do + it "is similar" do expect([1,2,3]).to have_same_elements_as([2,3,1]) end end @@ -278,7 +278,7 @@ Feature: Define a custom matcher Then the output should contain "3 examples, 0 failures" Scenario: Matcher with separate logic for expect().to and expect().not_to - Given a file named "matcher_with_separate_should_not_logic_spec.rb" with: + Given a file named "matcher_with_separate_not_to_logic_spec.rb" with: """ruby RSpec::Matchers.define :contain do |*expected| match do |actual| @@ -299,7 +299,7 @@ Feature: Define a custom matcher it { is_expected.not_to contain(1, 4) } end """ - When I run `rspec matcher_with_separate_should_not_logic_spec.rb` + When I run `rspec matcher_with_separate_not_to_logic_spec.rb` Then the output should contain all of these: | 4 examples, 2 failures | | expected [1, 2, 3] to contain 1 and 4 | diff --git a/features/customized_message.feature b/features/customized_message.feature index c96a3ea0f..ec40f8137 100644 --- a/features/customized_message.feature +++ b/features/customized_message.feature @@ -1,8 +1,7 @@ Feature: customized message RSpec tries to provide useful failure messages, but for cases in which you want more - specific information, you can define your own message right in the example.This works for - any matcher _other than the operator matchers_. + specific information, you can define your own message right in the example. Scenario: customize failure message Given a file named "example_spec.rb" with: diff --git a/lib/rspec/expectations/handler.rb b/lib/rspec/expectations/handler.rb index a8f7c0531..3ff9b129f 100644 --- a/lib/rspec/expectations/handler.rb +++ b/lib/rspec/expectations/handler.rb @@ -46,8 +46,6 @@ def self.handle_failure(matcher, message, failure_message_method) class PositiveExpectationHandler def self.handle_matcher(actual, initial_matcher, custom_message=nil, &block) ExpectationHelper.with_matcher(self, initial_matcher, custom_message) do |matcher| - return ::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher.new(actual) unless initial_matcher - match_result = matcher.matches?(actual, &block) if custom_message && match_result.respond_to?(:error_generator) match_result.error_generator.opts[:message] = custom_message @@ -74,8 +72,6 @@ def self.opposite_should_method class NegativeExpectationHandler def self.handle_matcher(actual, initial_matcher, custom_message=nil, &block) ExpectationHelper.with_matcher(self, initial_matcher, custom_message) do |matcher| - return ::RSpec::Matchers::BuiltIn::NegativeOperatorMatcher.new(actual) unless initial_matcher - negated_match_result = does_not_match?(matcher, actual, &block) if custom_message && negated_match_result.respond_to?(:error_generator) negated_match_result.error_generator.opts[:message] = custom_message diff --git a/lib/rspec/matchers.rb b/lib/rspec/matchers.rb index e25cdac62..444c83f11 100644 --- a/lib/rspec/matchers.rb +++ b/lib/rspec/matchers.rb @@ -501,9 +501,6 @@ def change(receiver=nil, message=nil, &block) # This works for collections. Pass in multiple args and it will only # pass if all args are found in collection. # - # @note This is also available using the `=~` operator with `should`, - # but `=~` is not supported with `expect`. - # # @example # expect([1, 2, 3]).to contain_exactly(1, 2, 3) # expect([1, 2, 3]).to contain_exactly(1, 3, 2) diff --git a/lib/rspec/matchers/built_in.rb b/lib/rspec/matchers/built_in.rb index e6237ff08..4cd7dfee9 100644 --- a/lib/rspec/matchers/built_in.rb +++ b/lib/rspec/matchers/built_in.rb @@ -35,10 +35,7 @@ module BuiltIn autoload :Include, 'rspec/matchers/built_in/include' autoload :All, 'rspec/matchers/built_in/all' autoload :Match, 'rspec/matchers/built_in/match' - autoload :NegativeOperatorMatcher, 'rspec/matchers/built_in/operators' - autoload :OperatorMatcher, 'rspec/matchers/built_in/operators' autoload :Output, 'rspec/matchers/built_in/output' - autoload :PositiveOperatorMatcher, 'rspec/matchers/built_in/operators' autoload :RaiseError, 'rspec/matchers/built_in/raise_error' autoload :RespondTo, 'rspec/matchers/built_in/respond_to' autoload :Satisfy, 'rspec/matchers/built_in/satisfy' diff --git a/lib/rspec/matchers/built_in/operators.rb b/lib/rspec/matchers/built_in/operators.rb deleted file mode 100644 index 188eb14a9..000000000 --- a/lib/rspec/matchers/built_in/operators.rb +++ /dev/null @@ -1,128 +0,0 @@ -require 'rspec/support' - -module RSpec - module Matchers - module BuiltIn - # @api private - # Provides the implementation for operator matchers. - # Not intended to be instantiated directly. - # Only available for use with `should` one-liner syntax. - class OperatorMatcher - class << self - # @private - def registry - @registry ||= {} - end - - # @private - def register(klass, operator, matcher) - registry[klass] ||= {} - registry[klass][operator] = matcher - end - - # @private - def unregister(klass, operator) - registry[klass] && registry[klass].delete(operator) - end - - # @private - def get(klass, operator) - klass.ancestors.each do |ancestor| - matcher = registry[ancestor] && registry[ancestor][operator] - return matcher if matcher - end - - nil - end - end - - register Enumerable, '=~', BuiltIn::ContainExactly - - def initialize(actual) - @actual = actual - end - - # @private - def self.use_custom_matcher_or_delegate(operator) - define_method(operator) do |expected| - if !has_non_generic_implementation_of?(operator) && (matcher = OperatorMatcher.get(@actual.class, operator)) - @actual.__send__(::RSpec::Matchers.last_expectation_handler.should_method, matcher.new(expected)) - else - eval_match(@actual, operator, expected) - end - end - - negative_operator = operator.sub(/^=/, '!') - if negative_operator != operator && respond_to?(negative_operator) - define_method(negative_operator) do |_expected| - opposite_should = ::RSpec::Matchers.last_expectation_handler.opposite_should_method - raise "RSpec does not support `#{::RSpec::Matchers.last_expectation_handler.should_method} #{negative_operator} expected`. " \ - "Use `#{opposite_should} #{operator} expected` instead." - end - end - end - - ['==', '===', '=~', '>', '>=', '<', '<='].each do |operator| - use_custom_matcher_or_delegate operator - end - - # @private - def fail_with_message(message) - RSpec::Expectations.fail_with(message, @expected, @actual) - end - - # @api private - # @return [String] - def description - "#{@operator} #{RSpec::Support::ObjectFormatter.format(@expected)}" - end - - private - - def has_non_generic_implementation_of?(op) - Support.method_handle_for(@actual, op).owner != ::Kernel - rescue NameError - false - end - - def eval_match(actual, operator, expected) - ::RSpec::Matchers.last_matcher = self - @operator, @expected = operator, expected - __delegate_operator(actual, operator, expected) - end - end - - # @private - # Handles operator matcher for positive expectations - class PositiveOperatorMatcher < OperatorMatcher - def __delegate_operator(actual, operator, expected) - if actual.__send__(operator, expected) - true - else - expected_formatted = RSpec::Support::ObjectFormatter.format(expected) - actual_formatted = RSpec::Support::ObjectFormatter.format(actual) - - if ['==', '===', '=~'].include?(operator) - fail_with_message("expected: #{expected_formatted}\n got: #{actual_formatted} (using #{operator})") - else - fail_with_message("expected: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}") - end - end - end - end - - # @private - # Handles operator matcher for negative expectations - class NegativeOperatorMatcher < OperatorMatcher - def __delegate_operator(actual, operator, expected) - return false unless actual.__send__(operator, expected) - - expected_formatted = RSpec::Support::ObjectFormatter.format(expected) - actual_formatted = RSpec::Support::ObjectFormatter.format(actual) - - fail_with_message("expected not: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}") - end - end - end - end -end diff --git a/lib/rspec/matchers/dsl.rb b/lib/rspec/matchers/dsl.rb index 641a637e9..c6119ac1b 100644 --- a/lib/rspec/matchers/dsl.rb +++ b/lib/rspec/matchers/dsl.rb @@ -373,7 +373,7 @@ module DefaultImplementations include BuiltIn::BaseMatcher::DefaultFailureMessages # @api private - # Used internally by objects returns by `should` and `should_not`. + # Used internally by handlers and compound matchers. def diffable? false end diff --git a/spec/rspec/expectations/expectation_target_spec.rb b/spec/rspec/expectations/expectation_target_spec.rb index 26e432896..e4c2b6755 100644 --- a/spec/rspec/expectations/expectation_target_spec.rb +++ b/spec/rspec/expectations/expectation_target_spec.rb @@ -68,13 +68,13 @@ module Expectations it 'does not support operator matchers from #to' do expect { expect(3).to == 3 - }.to raise_error(ArgumentError) + }.to raise_error(ArgumentError, /The expect syntax does not support operator matchers, so you must pass a matcher to `#to`/) end it 'does not support operator matchers from #not_to' do expect { expect(3).not_to == 4 - }.to raise_error(ArgumentError) + }.to raise_error(ArgumentError, /The expect syntax does not support operator matchers, so you must pass a matcher to `#not_to`/) end end diff --git a/spec/rspec/matchers/built_in/be_spec.rb b/spec/rspec/matchers/built_in/be_spec.rb index fd98aff07..d80a8eef8 100644 --- a/spec/rspec/matchers/built_in/be_spec.rb +++ b/spec/rspec/matchers/built_in/be_spec.rb @@ -746,20 +746,6 @@ def send end end -RSpec.describe "should be =~" do - subject { "a string" } - - it "passes when =~ operator returns true" do - should be =~ /str/ - end - - it "fails when =~ operator returns false" do - expect { - should be =~ /blah/ - }.to fail_with(%(expected: =~ /blah/\n got: "a string")) - end -end - RSpec.describe "expect(...).to be ===" do it "passes when === operator returns true" do expect(Hash).to be === {} diff --git a/spec/rspec/matchers/built_in/operators_spec.rb b/spec/rspec/matchers/built_in/operators_spec.rb deleted file mode 100644 index a24ab290b..000000000 --- a/spec/rspec/matchers/built_in/operators_spec.rb +++ /dev/null @@ -1,251 +0,0 @@ -class MethodOverrideObject - def method - :foo - end -end - -class MethodMissingObject < Struct.new(:original) - undef == - - def method_missing(name, *args, &block) - original.__send__ name, *args, &block - end -end - -RSpec.describe "operator matchers" do - describe "should ==" do - subject { 'apple'.dup } - - it "delegates message to target" do - expect(subject).to receive(:==).with("apple").and_return(true) - should == "apple" - end - - it "returns true on success" do - expect(should == "apple").to be(true) - end - - it "fails when target.==(actual) returns false" do - expect(RSpec::Expectations).to receive(:fail_with).with(%{expected: "orange"\n got: "apple" (using ==)}, "orange", "apple") - should == "orange" - end - - context "when #method is overriden" do - subject(:myobj) { MethodOverrideObject.new } - it { expect { should == myobj }.to_not raise_error } - end - - context "when implemented via method_missing" do - let(:obj) { Object.new } - subject(:myobj) { MethodMissingObject.new(obj) } - - it { should == obj } - it { should_not == Object.new } - end - end - - describe "unsupported operators" do - subject { "apple" } - - it "raises an appropriate error for should != expected" do - expect { - should != "pear" - }.to raise_error(/does not support `should != expected`. Use `should_not == expected`/) - end - - it "raises an appropriate error for should_not != expected" do - expect { - should_not != "pear" - }.to raise_error(/does not support `should_not != expected`. Use `should == expected`/) - end - - it "raises an appropriate error for should !~ expected" do - expect { - should !~ /regex/ - }.to raise_error(/does not support `should !~ expected`. Use `should_not =~ expected`/) - end - - it "raises an appropriate error for should_not !~ expected" do - expect { - should_not !~ /regex/ - }.to raise_error(/does not support `should_not !~ expected`. Use `should =~ expected`/) - end - end - - describe "should_not ==" do - subject { "orange" } - - it "delegates message to target" do - expect(subject).to receive(:==).with("apple").and_return(false) - should_not == "apple" - end - - it "returns false on success" do - expect(should_not == "apple").to be(false) - end - - it "fails when target.==(actual) returns true" do - expect(RSpec::Expectations).to receive(:fail_with).with(%(expected not: == "orange"\n got: "orange"), "orange", "orange") - should_not == "orange" - end - end - - describe "should ===" do - subject { "apple" } - - it "delegates message to target" do - expect(subject).to receive(:===).with("apple").and_return(true) - should === "apple" - end - - it "fails when target.===(actual) returns false" do - expect(subject).to receive(:===).with("orange").and_return(false) - expect(RSpec::Expectations).to receive(:fail_with).with(%{expected: "orange"\n got: "apple" (using ===)}, "orange", "apple") - should === "orange" - end - end - - describe "should_not ===" do - subject { "apple" } - - it "delegates message to target" do - expect(subject).to receive(:===).with("apple").and_return(false) - should_not === "apple" - end - - it "fails when target.===(actual) returns true" do - expect(subject).to receive(:===).with("apple").and_return(true) - expect(RSpec::Expectations).to receive(:fail_with).with(%(expected not: === "apple"\n got: "apple"), "apple", "apple") - should_not === "apple" - end - end - - describe "should =~" do - subject { "foo" } - - it "delegates message to target" do - expect(subject).to receive(:=~).with(/oo/).and_return(true) - should =~ /oo/ - end - - it "fails when target.=~(actual) returns false" do - expect(RSpec::Expectations).to receive(:fail_with).with(%{expected: /fu/\n got: "foo" (using =~)}, /fu/, "foo") - should =~ /fu/ - end - end - - describe "should_not =~" do - subject { "foo" } - - it "delegates message to target" do - expect(subject).to receive(:=~).with(/fu/).and_return(false) - should_not =~ /fu/ - end - - it "fails when target.=~(actual) returns false" do - expect(RSpec::Expectations).to receive(:fail_with).with(%(expected not: =~ /oo/\n got: "foo"), /oo/, "foo") - should_not =~ /oo/ - end - end - - describe "should >" do - subject { 4 } - - it "passes if > passes" do - should > 3 - end - - it "fails if > fails" do - expect(RSpec::Expectations).to receive(:fail_with).with("expected: > 5\n got: 4", 5, 4) - should > 5 - end - end - - describe "should >=" do - subject { 4 } - - it "passes if actual == expected" do - should >= 4 - end - - it "passes if actual > expected" do - should >= 3 - end - - it "fails if > fails" do - expect(RSpec::Expectations).to receive(:fail_with).with("expected: >= 5\n got: 4", 5, 4) - should >= 5 - end - end - - describe "should <" do - subject { 4 } - - it "passes if < passes" do - should < 5 - end - - it "fails if > fails" do - expect(RSpec::Expectations).to receive(:fail_with).with("expected: < 3\n got: 4", 3, 4) - should < 3 - end - end - - describe "should <=" do - subject { 4 } - - it "passes if actual == expected" do - should <= 4 - end - - it "passes if actual < expected" do - should <= 5 - end - - it "fails if > fails" do - expect(RSpec::Expectations).to receive(:fail_with).with("expected: <= 3\n got: 4", 3, 4) - should <= 3 - end - end - - describe "OperatorMatcher registry" do - let(:custom_klass) { Class.new } - let(:custom_subklass) { Class.new(custom_klass) } - - after { - RSpec::Matchers::BuiltIn::OperatorMatcher.unregister(custom_klass, "=~") - } - - it "allows operator matchers to be registered for classes" do - RSpec::Matchers::BuiltIn::OperatorMatcher.register(custom_klass, "=~", RSpec::Matchers::BuiltIn::Match) - expect(RSpec::Matchers::BuiltIn::OperatorMatcher.get(custom_klass, "=~")).to eq(RSpec::Matchers::BuiltIn::Match) - end - - it "considers ancestors when finding an operator matcher" do - RSpec::Matchers::BuiltIn::OperatorMatcher.register(custom_klass, "=~", RSpec::Matchers::BuiltIn::Match) - expect(RSpec::Matchers::BuiltIn::OperatorMatcher.get(custom_subklass, "=~")).to eq(RSpec::Matchers::BuiltIn::Match) - end - - it "returns nil if there is no matcher registered for a class" do - expect(RSpec::Matchers::BuiltIn::OperatorMatcher.get(custom_klass, "=~")).to be_nil - end - end - - describe RSpec::Matchers::BuiltIn::PositiveOperatorMatcher do - subject(:o) { Object.new } - - it "works when the target has implemented #send" do - def o.send(*_args); raise "DOH! Library developers shouldn't use #send!" end - expect { should == o }.not_to raise_error - end - end - - describe RSpec::Matchers::BuiltIn::NegativeOperatorMatcher do - subject(:o) { Object.new } - - it "works when the target has implemented #send" do - def o.send(*_args); raise "DOH! Library developers shouldn't use #send!" end - expect { should_not == :foo }.not_to raise_error - end - end -end