In my RSpec tests I often have the challenge to compare deep nested hashes like this
{ foo: ["test", { bar: [1,2,3] }] }
The values [1,2,3]
are read from a DB where the order is not guaranteed, nor do I care about the order. So when I compare the two hashes in my test, I always have to make sure on both sides that the order is enforced:
# my_class.rb
class MyClass
def self.data
read_db.sort
end
end
expected_data = { foo: ["test", { bar: [1,2,3].sort }] }
expect(MyClass.data).to eq(expected_data)
I really dislike the fact, that I have to alter my production code only because of my test env.
I could of course stop comparing the whole hash and focus on the single keys, and therefore could remove the custom sorting inside my production code:
actual_data = MyClass.data
expect(actual_data.fetch(:foo)[0]).to eq("test")
expect(actual_data.fetch(:foo)[1].fetch(:bar)).to match_array([1,2,3])
But this makes the whole spec pretty complicated and hard to read.
So I thought about creating a custom "unordered array" class Bag
that, when it get's compared, ignores the order:
class Bag < Array
def eql?(other)
sort_by(&:hash) == other.sort_by(&:hash)
end
alias == eql?
end
But this works only, when the Bag
class is on the left side of the comparison:
expect(Bag.new([1, "2"])).to eq(["2", 1])
1 example, 0 failures
But that's usually not the case, as the expected value in a test should be inside expect(...)
, which represents the values from the DB:
expect(["2", 1]).to eq(Bag.new([1, "2"]))
Failure/Error: expect(["2", 1]).to eq(Bag.new([1, "2"]))
expected: [1, "2"]
got: ["2", 1]
(compared using ==)
1 example, 1 failure
The reason behind this is, that Array#==
is called and not my custom Bag#==
method.
I looked into the docs (https://devdocs.io/ruby~3.2/array#method-i-3D-3D) where it states
Returns true if both array.size == other_array.size and for each index i in array, array[i] == other_array[i]:
But here I came to a stop, as I wasn't able to figure out, how to implement the fetching of a value for a specific index. I tried implementing Bar#[]
and Bar#fetch
but they aren't called when comparing objects.
Maybe it's not possible at all because array calls some low level C function that can't be overriden. But maybe someone knows a solution.