splitwise/super_diff

match_array diffs are much larger than eq diffs

maxnotarangelo opened this issue · 2 comments

I just started using this gem, and I noticed that while it does a great job with showing only the differences in array elements when you use the eq matcher in RSpec, it just shows the entirety of each different element when you use match_array. It would be quite useful if it was able to show a similarly clean diff.

Here's an example.

require "rails_helper"

RSpec.describe Book, type: :model do
  it "matches" do
    new_books = Fabricate.times(2, :book, published_date: 1.day.ago)
    old_books = Fabricate.times(2, :book, published_date: 1.year.ago)

    expect(new_books).to eq(old_books)
  end
end

This produces the following output (I have the diff elision setting enabled):

Screen Shot 2022-05-06 at 3 19 50 PM

However, switching the expectation to

expect(new_books).to match_array(old_books)

produces a much larger diff, which is much harder to read:

Screen Shot 2022-05-06 at 3 21 38 PM

Screen Shot 2022-05-06 at 3 21 49 PM

Screen Shot 2022-05-06 at 3 22 10 PM

Hi @maxnotarangelo,

Thanks for bringing this up. From a matcher perspective, match_array works on a different premise than eq.

In the case of eq, we can assume there is a one-to-one correlation between the items in the expected and actual arrays. So even if you have two separate objects with different contents, we can assume that there is a relationship between the two — i.e., you either meant the "expected" version or the "actual" version — and so we ask you to choose one.

match_array is different, though. The docs for match_array say:

An alternate form of contain_exactly that accepts the expected contents as a single array arg rather that splatted out as individual items.

The docs for contain_exactly say:

Passes if actual contains all of the expected regardless of order.

So when you use match_array (or contain_exactly), you're saying, I don't know in which order the items in the array I'm providing will appear, and I don't care. So we can't make the same assumptions as for eq. In other words, if there are two items, one in expected, the other in actual, at the same index, we need to know that they are similar enough to each other that we can put them next to each other in the diff. In your example, this is a bit difficult. In fact we could say all of the objects in the array are similar to each other because they have the same attributes, so there's really no "best" way to arrange them.

Because of this, there's not a great way for us to get match_array to act like eq.

If you want to keep using eq but achieve the same goals as match_array, you could sort both of your arrays before making the assertion:

expect(new_books.sort_by(&:created_at)).to eq(old_books.sort_by(&:created_at))

This might not help your diff right now, because both arrays are completely different, but it might help when you're doing this sort of thing in the future.

Does that make sense?

Yeah, that makes sense. I was thinking that there might be some standard ordering of the array elements that match_array used, but even if there were, it wouldn't necessarily be a useful way to sort it.

Thanks for the explanation!