Minitest Hash Diff Hard to Read? Here's How to Actually Compare Them
You ran your test suite, something failed, and Minitest handed you this:
Expected: {"id"=>42, "email"=>"user@example.com", "name"=>"Jordan", "role"=>"admin", "active"=>true, "verified"=>false, "plan"=>"pro"}
Actual: {"id"=>42, "email"=>"user@example.com", "name"=>"Jordan", "role"=>"admin", "active"=>true, "verified"=>true, "plan"=>"pro"}
Two lines, nearly identical, and somewhere in there is the one value that broke your test. If you have ever lost five minutes dragging your cursor across the screen comparing characters, this post is for you. Here is why Minitest output is so hard to read, and a few ways to make it readable again.
Why the output is so hard to read
Minitest prints a failed assert_equal by calling inspect on both values and showing them on two lines. For hashes, inspect produces a single unwrapped line in insertion order. That design has three consequences that work against you:
- No wrapping or indentation. A hash with ten keys becomes one long line. Your eyes have no anchors, so comparing the two lines means scanning left to right, character by character.
- Insertion order is preserved, not normalized. If the two hashes were built in different orders, the keys appear in different positions, and the diff looks totally different even when the data is the same.
- Types are nearly invisible.
"42"and42differ by two quote characters.trueandfalsediffer by a couple of letters buried mid-line. These are exactly the differences that fail a test, and exactly the ones hardest to see.
In the example above, verified flipped from false to true. Now that you know where to look it is obvious. Finding it cold is the hard part.
Quick wins inside your test
A few things help before you reach for any tool.
Pretty-print the hashes
Pipe the values through pp or JSON.pretty_generate to get them onto multiple lines:
require "json"
puts JSON.pretty_generate(actual)
This breaks the wall of text into one key per line, which at least gives your eyes anchors. It does not align the two hashes for comparison, but it is better than a single line.
Use a better assertion
assert_equal on large hashes gives you the raw dump. If you only care about a few keys, assert on those specifically:
assert_equal "admin", user_hash["role"]
assert_equal true, user_hash["active"]
Smaller assertions produce smaller, readable failures. The tradeoff is that you stop checking the whole structure, so use this when you genuinely only care about a subset.
Sort the keys before comparing
If your failures are caused by key ordering rather than real differences, sort both hashes first:
assert_equal expected.sort.to_h, actual.sort.to_h
This removes ordering noise so a real difference, if there is one, is not buried among dozens of false ones.
The fastest fix: a real side-by-side diff
When the hashes are big or nested, the quickest path is to paste them into a tool built for exactly this. RubyHash takes the two lines straight from your Minitest output, including the Expected: and Actual: labels, and:
- parses the Ruby hash syntax into a real structure,
- sorts the keys so ordering noise disappears,
- renders a side-by-side diff with the changed value highlighted, and
- flags any type changes, like a
falsethat becametrueor anilthat became a string.
For the example at the top of this post, you would paste both lines and immediately see a single highlighted row: verified changed from false to true. No scanning, no squinting.
It runs entirely in your browser, so the hashes never leave your machine, which matters when the data in a failing test is real customer data.
When the diff points somewhere surprising
Once you can read the difference clearly, the next question is which side is wrong. A readable diff makes this a quick judgment instead of a guess:
- A changed value usually means a logic bug in the code, or a stale expectation in the test.
- A type change (
"3"versus3,:adminversus"admin") usually points at serialization, JSON parsing, or symbol-versus-string key confusion. - A missing or extra key often means a conditional that did or did not run, or a serializer that gained or lost a field.
Classifying the difference tells you where to start looking, which is the whole point of getting a clean diff in the first place.
Or fix it permanently with a gem
If you would rather never see the unreadable version again, minitest-hashdiff replaces the default output for failing hash assertions with a sorted, type-aware report, right in the terminal:
Hash diff (expected => actual):
changed "last_login": nil => "2026-07-10" (nil -> String)
changed "role": "admin" => "editor"
Add gem "minitest-hashdiff" to your test group and you are done; Minitest discovers it automatically, and only hash assertions are affected.
Stop reading hashes in the terminal
The terminal is great for running tests and terrible for comparing their output. You do not have to debug where the failure happened to appear. Next time Minitest dumps two hashes you cannot tell apart, copy both lines, paste them into RubyHash, and let it show you the one thing that changed. A ten-minute squint becomes a two-second glance, and you get back to fixing the actual bug.
Comparing Ruby hashes in a failing test and need to spot the difference fast? Try RubyHash, paste your two hashes for a clean, side-by-side diff.
Enjoyed this post?
Subscribe to get notified when we publish more Ruby and Rails content.