From e435a1db5a754a2fc8b458868a96d25c4f689c83 Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune Date: Tue, 16 Jun 2020 17:24:33 -0400 Subject: [PATCH] Factorize PredicateMatcher. Improve failure message for `Has`. Improve example in predicates.feature --- features/built_in_matchers/predicates.feature | 25 ++++++++-------- lib/rspec/matchers/built_in/has.rb | 30 ------------------- spec/rspec/matchers/built_in/has_spec.rb | 14 ++++----- .../matchers/description_generation_spec.rb | 2 +- 4 files changed, 21 insertions(+), 50 deletions(-) diff --git a/features/built_in_matchers/predicates.feature b/features/built_in_matchers/predicates.feature index e417bdf6a..317ffeec8 100644 --- a/features/built_in_matchers/predicates.feature +++ b/features/built_in_matchers/predicates.feature @@ -88,32 +88,33 @@ Feature: Predicate matchers """ When I run `rspec should_have_key_spec.rb` Then the output should contain "2 examples, 1 failure" - And the output should contain "expected #has_key?(:bar) to return true, got false" + And the output should contain "expected `{:foo=>7}.has_key?(:bar)` to return true, 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: """ruby - class Hash - def has_all_string_keys? - keys.all? { |k| String === k } + class Float + def has_decimals? + round != self end end - RSpec.describe Hash do - context 'with symbol keys' do - subject { { :foo => 7, :bar => 5 } } - it { is_expected.not_to have_all_string_keys } + RSpec.describe Float do + context 'with decimals' do + subject { 4.2 } + + it { is_expected.to have_decimals } end - context 'with string keys' do - subject { { 'foo' => 7, 'bar' => 5 } } - it { is_expected.not_to have_all_string_keys } # deliberate failure + context 'with no decimals' do + subject { 42.0 } + it { is_expected.to have_decimals } # deliberate failure end end """ When I run `rspec should_not_have_all_string_keys_spec.rb` Then the output should contain "2 examples, 1 failure" - And the output should contain "expected #has_all_string_keys? to return false, got true" + And the output should contain "expected `42.0.has_decimals?` to return true, got false" Scenario: matcher arguments are passed on to the predicate method Given a file named "predicate_matcher_argument_spec.rb" with: diff --git a/lib/rspec/matchers/built_in/has.rb b/lib/rspec/matchers/built_in/has.rb index d002d3381..63623d02f 100644 --- a/lib/rspec/matchers/built_in/has.rb +++ b/lib/rspec/matchers/built_in/has.rb @@ -130,37 +130,7 @@ def failure_to_respond_explanation class Has < DynamicPredicate # :nodoc: REGEX = Matchers::HAS_REGEX - - # @api private - # @return [String] - def failure_message - validity_message || "expected ##{predicate}#{failure_message_args_description} to return true, got false" - end - - # @api private - # @return [String] - def failure_message_when_negated - validity_message || "expected ##{predicate}#{failure_message_args_description} to return false, got true" - end - - # @api private - # @return [String] - def description - [method_description, args_description].compact.join(' ') - end - private - - def args_description - return nil if @args.empty? - @args.map { |arg| RSpec::Support::ObjectFormatter.format(arg) }.join(', ') - end - - def failure_message_args_description - desc = args_description - "(#{desc})" if desc - end - def predicate @predicate ||= :"has_#{root}?" end diff --git a/spec/rspec/matchers/built_in/has_spec.rb b/spec/rspec/matchers/built_in/has_spec.rb index 55505e8bb..eba5cb54d 100644 --- a/spec/rspec/matchers/built_in/has_spec.rb +++ b/spec/rspec/matchers/built_in/has_spec.rb @@ -29,7 +29,7 @@ it "fails if #has_sym?(*args) returns false" do expect { expect({ :b => "B" }).to have_key(:a) - }.to fail_with("expected #has_key?(:a) to return true, got false") + }.to fail_with('expected `{:b=>"B"}.has_key?(:a)` to return true, got false') end obj_with_block_method = Object.new @@ -65,7 +65,7 @@ def obj_with_block_method.has_some_stuff?; yield; end def o.has_some_stuff?; false; end expect { expect(o).to have_some_stuff - }.to fail_with("expected #has_some_stuff? to return true, got false") + }.to fail_with("expected `#{o.inspect}.has_some_stuff?` to return true, got false") end it 'includes multiple args in the failure message if multiple args were given to the matcher' do @@ -73,7 +73,7 @@ def o.has_some_stuff?; false; end def o.has_some_stuff?(*_); false; end expect { expect(o).to have_some_stuff(:a, 7, "foo") - }.to fail_with('expected #has_some_stuff?(:a, 7, "foo") to return true, got false') + }.to fail_with(%Q{expected `#{o.inspect}.has_some_stuff?(:a, 7, "foo")` to return true, got false}) end it "fails if #has_sym?(*args) returns nil" do @@ -83,7 +83,7 @@ def has_foo? end expect { expect(klass.new).to have_foo - }.to fail_with(/expected #has_foo.* to return true, got false/) + }.to fail_with(/expected `.*\.has_foo\?` to return true, got nil/) end it 'fails if #has_sym?(*args) is private' do @@ -150,7 +150,7 @@ def has_foo? it "fails if #has_sym?(*args) returns true" do expect { expect({ :a => "A" }).not_to have_key(:a) - }.to fail_with("expected #has_key?(:a) to return false, got true") + }.to fail_with('expected `{:a=>"A"}.has_key?(:a)` to return false, got true') end it "fails if target does not respond to #has_sym?" do @@ -174,7 +174,7 @@ def o.has_sym?(*_args) def o.has_some_stuff?; true; end expect { expect(o).not_to have_some_stuff - }.to fail_with("expected #has_some_stuff? to return false, got true") + }.to fail_with("expected `#{o.inspect}.has_some_stuff?` to return false, got true") end it 'includes multiple args in the failure message if multiple args were given to the matcher' do @@ -182,7 +182,7 @@ def o.has_some_stuff?; true; end def o.has_some_stuff?(*_); true; end expect { expect(o).not_to have_some_stuff(:a, 7, "foo") - }.to fail_with('expected #has_some_stuff?(:a, 7, "foo") to return false, got true') + }.to fail_with(%Q{expected `#{o.inspect}.has_some_stuff?(:a, 7, "foo")` to return false, got true}) end end diff --git a/spec/rspec/matchers/description_generation_spec.rb b/spec/rspec/matchers/description_generation_spec.rb index 016d2595d..1dc1103c1 100644 --- a/spec/rspec/matchers/description_generation_spec.rb +++ b/spec/rspec/matchers/description_generation_spec.rb @@ -103,7 +103,7 @@ def object.has_eyes_closed?; true; end def object.has_taste_for?(*_args); true; end expect(object).to have_taste_for("wine", "cheese") - expect(RSpec::Matchers.generated_description).to eq 'is expected to have taste for "wine", "cheese"' + expect(RSpec::Matchers.generated_description).to eq 'is expected to have taste for "wine" and "cheese"' end example "expect(...).to include(x)" do