spec/support/matchers/match_object.rb
RSpec::Matchers.define :match_object do |expected|
match do |actual|
expected == actual
end
failure_message do |actual|
break if actual == expected
object_eq(actual, expected)
@error_message.join("\n")
end
def object_eq(actual, expected, path=[])
@error_message ||= []
types = [actual, expected].map(&:class).uniq
if types == [Hash]
compare_arrays(actual.keys, expected.keys, path, actual)
expected.each do |k, expected_val|
object_eq(actual[k], expected_val, path + [k])
end
elsif types == [Array]
if compare_arrays(actual, expected, path)
expected.each_with_index do |expected_el, i|
object_eq(actual[i], expected_el, path + ["[#{i}]"])
end
end
elsif types.size == 1 # same class
@error_message << "Mismatched values in #{path}:"
@error_message << "\t actual=#{actual}"
@error_message << "\texpected=#{expected}"
else
@error_message << "Mismatched types in #{path}:"
@error_message << "\t actual=#{types[0]} value=#{actual.inspect}"
@error_message << "\texpected=#{types[1]} value=#{expected.inspect}"
end
end
def compare_arrays(actual, expected, path, context=actual)
if expected.size == actual.size
true
else
@error_message << "Extra/missing elements in #{path}:"
@error_message << "\tactual=#{actual.size} expected=#{expected.size}"
@error_message << "\textra=#{actual - expected}"
@error_message << "\tmissing=#{expected - actual}"
@error_message << "\tcontext=#{context}"
false
end
end
end