Skip to content

Instantly share code, notes, and snippets.

@groyoh
Last active August 29, 2015 14:24
Show Gist options
  • Save groyoh/7869593e40c63af885ec to your computer and use it in GitHub Desktop.
Save groyoh/7869593e40c63af885ec to your computer and use it in GitHub Desktop.
Better hash matcher?
require 'bundler/inline'
gemfile(true) do
source 'https://rubygems.org'
gem 'activesupport', require: 'active_support'
gem 'colorize'
end
require 'colorize'
require 'active_support/core_ext/object/deep_dup'
RSpec::Matchers.define :match_hash do |expected|
match do |actual|
actual == expected
end
failure_message do |actual|
"Non matching:\n#{match(actual, expected)}"
end
def key(k)
klass = k.class
if klass == Symbol
":#{k}"
elsif klass == String
"\"#{k}\""
else
k
end
end
def desc(prefix, suffix, value)
"\t * #{prefix}#{suffix} # => #{value}"
end
def match(actual, expected, suffix = "")
message = ""
if actual.is_a?(Hash) && expected.is_a?(Hash)
expected.each do |k,v|
puts key(k)
next_suffix = "#{suffix}[#{key(k)}]"
message << match(actual[k], v, next_suffix)
end
actual.each do |k,v|
next if expected.has_key?(k)
actual_desc = desc("actual", "#{suffix}[#{key(k)}]", v).green
message << "- actual#{suffix} has extra key #{key(k)}\n#{actual_desc}\n"
end
expected.each do |k,v|
next if actual.has_key?(k)
expected_desc = desc("expected", "#{suffix}[#{key(k)}]", v).red
message << "- actual#{suffix} is missing key #{key(k)}\n#{expected_desc}\n"
end
elsif actual.is_a?(Array) && expected.is_a?(Array)
expected_size = expected.size
actual_size = actual.size
actual.size.times do |i|
next_suffix = "#{suffix}[#{i}]"
message << match(actual[i], expected[i], next_suffix)
end
if expected_size > actual_size
(actual_size...expected_size).each do |i|
expected_desc = desc("expect", "#{suffix}[#{i}]", expected[i]).red
message << "- actual#{suffix} is missing element at index #{i}\n#{expected_desc}\n"
end
elsif actual_size > expected_size
(expected_size...actual_size).each do |i|
actual_desc = desc("actual", "#{suffix}[#{i}]", actual[i]).green
message << "- actual#{suffix} has extra element at index #{i}\n#{actual_desc}\n"
end
end
message
elsif actual != expected
actual_desc = desc("actual", suffix, actual.inspect).green
expected_desc = "\t * expect#{suffix} # => #{expected.inspect}".red
message << "- actual#{suffix} != expected#{suffix}:\n#{actual_desc}\n#{expected_desc}\n"
end
message
end
end
H = {
meta: {
per_page: 1
},
data: [{
id: "123",
type: "user",
attributes: {
friends_count: 2,
},
relationships: {
friends: {
data:[
{ id: "12", type: "user" },
{ id: "13", type: "user" }
]
}
}
}]
}
h1 = H.deep_dup
h1.delete(:meta)
RSpec.describe h1 do
it { is_expected.to match_hash(H) }
end
h2 = H.deep_dup
h2[:meta][:next_page] = 2
RSpec.describe h2 do
it { is_expected.to match_hash(H) }
end
h3 = H.deep_dup
h3[:data][0][:relationships][:friends][:data].pop
RSpec.describe h3 do
it { is_expected.to match_hash(H) }
end
h4 = H.deep_dup
h4[:data][0][:relationships][:friends][:data] << { id: "15", type: "user" }
RSpec.describe h4 do
it { is_expected.to match_hash(H) }
end
h5 = H.deep_dup
h5[:data][0][:attributes].delete(:friends_count)
RSpec.describe h5 do
it { is_expected.to match_hash(H) }
end
h6 = H.deep_dup
h6[:data][0][:attributes]["is_admin"] = true
RSpec.describe h6 do
it { is_expected.to match_hash(H) }
end
__END__
Failures:
1) {:data=>[{:id=>"123", :type=>"user", :attributes=>{:friends_count=>2}, :relationships=>{:friends=>{:data=>[{:id=>"12", :type=>"user"}, {:id=>"13", :type=>"user"}]}}}]} should match hash [:meta, {:per_page=>1}] and [:data, [{:id=>"123", :type=>"user", :attributes=>{:friends_count=>2}, :relationships=>{:friends=>{:data=>[{:id=>"12", :type=>"user"}, {:id=>"13", :type=>"user"}]}}}]]
Failure/Error: it { is_expected.to match_hash(H) }
Non matching:
- actual[:meta] != expected[:meta]:
* actual[:meta] # => nil
* expect[:meta] # => {:per_page=>1}
- actual is missing key :meta
* expected[:meta] # => {:per_page=>1}
# ./better_hash_matcher.rb:109:in `block (2 levels) in <top (required)>'
2) {:meta=>{:per_page=>1, :next_page=>2}, :data=>[{:id=>"123", :type=>"user", :attributes=>{:friends_count=>2}, :relationships=>{:friends=>{:data=>[{:id=>"12", :type=>"user"}, {:id=>"13", :type=>"user"}]}}}]} should match hash [:meta, {:per_page=>1}] and [:data, [{:id=>"123", :type=>"user", :attributes=>{:friends_count=>2}, :relationships=>{:friends=>{:data=>[{:id=>"12", :type=>"user"}, {:id=>"13", :type=>"user"}]}}}]]
Failure/Error: it { is_expected.to match_hash(H) }
Non matching:
- actual[:meta] has extra key :next_page
* actual[:meta][:next_page] # => 2
# ./better_hash_matcher.rb:115:in `block (2 levels) in <top (required)>'
3) {:meta=>{:per_page=>1}, :data=>[{:id=>"123", :type=>"user", :attributes=>{:friends_count=>2}, :relationships=>{:friends=>{:data=>[{:id=>"12", :type=>"user"}]}}}]} should match hash [:meta, {:per_page=>1}] and [:data, [{:id=>"123", :type=>"user", :attributes=>{:friends_count=>2}, :relationships=>{:friends=>{:data=>[{:id=>"12", :type=>"user"}, {:id=>"13", :type=>"user"}]}}}]]
Failure/Error: it { is_expected.to match_hash(H) }
Non matching:
- actual[:data][0][:relationships][:friends][:data] is missing element at index 1
* expect[:data][0][:relationships][:friends][:data][1] # => {:id=>"13", :type=>"user"}
# ./better_hash_matcher.rb:121:in `block (2 levels) in <top (required)>'
4) {:meta=>{:per_page=>1}, :data=>[{:id=>"123", :type=>"user", :attributes=>{:friends_count=>2}, :relationships=>{:friends=>{:data=>[{:id=>"12", :type=>"user"}, {:id=>"13", :type=>"user"}, {:id=>"15", :type=>"user"}]}}}]} should match hash [:meta, {:per_page=>1}] and [:data, [{:id=>"123", :type=>"user", :attributes=>{:friends_count=>2}, :relationships=>{:friends=>{:data=>[{:id=>"12", :type=>"user"}, {:id=>"13", :type=>"user"}]}}}]]
Failure/Error: it { is_expected.to match_hash(H) }
Non matching:
- actual[:data][0][:relationships][:friends][:data][2] != expected[:data][0][:relationships][:friends][:data][2]:
* actual[:data][0][:relationships][:friends][:data][2] # => {:id=>"15", :type=>"user"}
* expect[:data][0][:relationships][:friends][:data][2] # => nil
- actual[:data][0][:relationships][:friends][:data] has extra element at index 2
* actual[:data][0][:relationships][:friends][:data][2] # => {:id=>"15", :type=>"user"}
# ./better_hash_matcher.rb:127:in `block (2 levels) in <top (required)>'
5) {:meta=>{:per_page=>1}, :data=>[{:id=>"123", :type=>"user", :attributes=>{}, :relationships=>{:friends=>{:data=>[{:id=>"12", :type=>"user"}, {:id=>"13", :type=>"user"}]}}}]} should match hash [:meta, {:per_page=>1}] and [:data, [{:id=>"123", :type=>"user", :attributes=>{:friends_count=>2}, :relationships=>{:friends=>{:data=>[{:id=>"12", :type=>"user"}, {:id=>"13", :type=>"user"}]}}}]]
Failure/Error: it { is_expected.to match_hash(H) }
Non matching:
- actual[:data][0][:attributes][:friends_count] != expected[:data][0][:attributes][:friends_count]:
* actual[:data][0][:attributes][:friends_count] # => nil
* expect[:data][0][:attributes][:friends_count] # => 2
- actual[:data][0][:attributes] is missing key :friends_count
* expected[:data][0][:attributes][:friends_count] # => 2
# ./better_hash_matcher.rb:133:in `block (2 levels) in <top (required)>'
6) {:meta=>{:per_page=>1}, :data=>[{:id=>"123", :type=>"user", :attributes=>{:friends_count=>2, "is_admin"=>true}, :relationships=>{:friends=>{:data=>[{:id=>"12", :type=>"user"}, {:id=>"13", :type=>"user"}]}}}]} should match hash [:meta, {:per_page=>1}] and [:data, [{:id=>"123", :type=>"user", :attributes=>{:friends_count=>2}, :relationships=>{:friends=>{:data=>[{:id=>"12", :type=>"user"}, {:id=>"13", :type=>"user"}]}}}]]
Failure/Error: it { is_expected.to match_hash(H) }
Non matching:
- actual[:data][0][:attributes] has extra key "is_admin"
* actual[:data][0][:attributes]["is_admin"] # => true
# ./better_hash_matcher.rb:139:in `block (2 levels) in <top (required)>'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment