Skip to content
This repository has been archived by the owner on Nov 30, 2024. It is now read-only.

Show attribute details when composed have_attributes fails #1371

Closed
Closed
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
11 changes: 8 additions & 3 deletions lib/rspec/matchers/built_in/have_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module BuiltIn
# Not intended to be instantiated directly.
class HaveAttributes < BaseMatcher
# @private
attr_reader :respond_to_failed
attr_reader :respond_to_failed, :match_failed

def initialize(expected)
@expected = expected
Expand Down Expand Up @@ -42,6 +42,9 @@ def does_not_match?(actual)
# @return [String]
def description
described_items = surface_descriptions_in(expected)

return improve_hash_formatting "have attributes #{RSpec::Support::ObjectFormatter.format(described_items)} but had attributes #{ formatted_values }" if match_failed

improve_hash_formatting "have attributes #{RSpec::Support::ObjectFormatter.format(described_items)}"
end

Expand All @@ -55,7 +58,7 @@ def diffable?
# @return [String]
def failure_message
respond_to_failure_message_or do
"expected #{actual_formatted} to #{description} but had attributes #{ formatted_values }"
"expected #{actual_formatted} to #{description}"
end
end

Expand All @@ -77,9 +80,11 @@ def cache_all_values

def perform_match(predicate)
cache_all_values
expected.__send__(predicate) do |attribute_key, attribute_value|
result = expected.__send__(predicate) do |attribute_key, attribute_value|
actual_has_attribute?(attribute_key, attribute_value)
end
@match_failed = !result
result
end

def actual_has_attribute?(attribute_key, attribute_value)
Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/matchers/composable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def surface_descriptions_in(item)
# this method when using `values_match?`, so that any memoization
# does not "leak" between checks.
def with_matchers_cloned(object)
if Matchers.is_a_matcher?(object)
if Matchers.is_a_matcher?(object) && !(::RSpec::Matchers::BuiltIn::HaveAttributes === object)
object.clone
elsif Hash === object
Hash[with_matchers_cloned(object.to_a)]
Expand Down
12 changes: 12 additions & 0 deletions spec/rspec/matchers/built_in/have_attributes_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@ def count
}.to fail_including("expected #{object_inspect person} to have attributes {:age => (a value < 10)}")
end
end

describe "expect([...]).to match(have_attributes(with_one_attribute))" do
it "fails with a clear message when attributes does not match" do
expect {
expect(person).to have_attributes(:age => 10)
}.to fail_including("expected #{object_inspect person} to have attributes {:age => 10} but had attributes {:age => 33}")

expect {
expect([person]).to match [have_attributes(:age => 10)]
}.to fail_including("expected [#{object_inspect person}] to match [(have attributes {:age => 10} but had attributes {:age => 33})]")
end
end
end

describe "expect(...).to_not have_attributes(with_one_attribute)" do
Expand Down
6 changes: 5 additions & 1 deletion spec/support/shared_examples/value_matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ def invalid_expectation

it 'can be used in a composed matcher expression' do
expect([valid_value, invalid_value]).to include(matcher)
# matcher.description => "have attributes {:name => \"Correct name\"}"

expect([invalid_value]).not_to include(matcher)
# matcher.description => "have attributes {:name => \"Correct name\"} but had attributes {:name => \"Wrong Name\"}"

expect {
expect([invalid_value]).to include(matcher)
}.to fail_including("include (#{matcher.description})")
}.to fail_including("include (#{matcher.description})") # matcher.description contains description of the most recent assertion
end

it 'uses the `ObjectFormatter` for `failure_message`' do
Expand Down