I have two hashes h1
and h2
that I'd like to compare in RSpec.
I want to check that the elements of h1
are the same as h2
after some transformation, which we'll call f
. That is, I want to verify that for every key k
in h1
, h1[k] == f(h2[k])
.
For example, if all the values in h2
are twice as big as the corresponding values in h1
, then I want to check that for every key k
in h1, h2[k] == h1[k] * 2
.
What's the right way to do this in RSpec? Currently I do:
h1 = ...
expect(
h2.all? { |k,v|
v == f(h1[k])
}
).to be true
but that seems clunky.
if anyone comes looking for a better diff for big nested hashes, I've came up with this:
RSpec::Matchers.define :be_a_hash_like do |expected_hash|
match do |actual_hash|
matching_results = actual_hash == expected_hash
unless matching_results
system(
"git diff $(echo '#{JSON.pretty_generate(expected_hash)}' | git hash-object -w --stdin) " +
"$(echo '#{JSON.pretty_generate(actual_hash)}' | git hash-object -w --stdin) --word-diff",
out: $stdout,
err: :out
)
end
matching_results
end
failure_message { 'Look at the Diff above! ^^^' }
end
# then use:
expect(actual).to be_a_hash_like(expected)
original gist: https://gist.github.com/fabriciofreitag/b7458725ffef08eaf5ea541c95385a92
Output example:
Sounds like what you are testing is the transformation. I would consider writing something like this:
it "transforming something does something" do
base_data = { k1: 1, k2: 2 }
transformed_data = base_data.each_with_object({}) { |(k, v), t|
t[k] = f(v)
}
expect(transformed_data).to eq(
k1: 2,
k2: 4,
)
end
To me the description clearly states what we are expecting. Then I can easily see from the test what the input is and the expected output. Also, this leverages the hash matcher which will provide a nice diff of the two hashes on a failure:
expected: {:k1=>2, :k2=>4}
got: {:k1=>1, :k2=>4}
(compared using ==)
Diff:
@@ -1,3 +1,3 @@
-:k1 => 2,
+:k1 => 1,
:k2 => 4,
Though I would question what the key-value relationship means. Are these simply test cases you are trying to run through? If so, I'd just make each a unique tests. If there is something more to it, then I may question why the transform method isn't provided the hash to start with.
We are using hashdiff to deal with this problem in RSpecs.
We have developed a custom matcher:
RSpec::Matchers.define :match_hash do |expected|
match do |actual|
Hashdiff.best_diff(expected, actual).blank?
end
failure_message do |actual|
"expected that #{actual} would match #{expected}, but it has these differences: #{Hashdiff.best_diff(expected, actual)}"
end
end
and we use it like this: expect(actual_hash).to match_hash(expected_hash)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With