Compares the structure of two deeply nested Ruby structures
Ruby
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib
test
.gitignore
.simplecov
Gemfile
Guardfile
LICENSE
README.md
Rakefile
structure_compare.gemspec

README.md

structure_compare

Compares the structure of two deep nested Ruby structures

General

Use case: you're writing an API response or a JSON export and want to unit test it. Optionally you can ignore the leaf values or any hash key types/order (see below).

Gives error results with the path to where exactly the structures differ.

This README file is mirrored on my blog

Installation

gem install structure_compare

or add it to your Gemfile:

gem structure_compare

quick-n-dirty example:

require 'structure_compare'
comparison = StructureCompare::StructureComparison.new

expected = { a: 1, b: 2, c: [1, 2, 3] }
actual   = { a: 1, b: 2, c: [1, 2, "A"] }

comparison.structures_are_equal?(expected, actual)
puts comparison.error
# => root[:c][2] : expected String to be kind of Fixnum

MiniTest

require 'structure_compare'
require 'structure_compare/minitest'

assert_structures_equal({ a: 1, b: 2 }, { a: 1, b: 2 })
refute_structures_equal({ a: 1, b: 2 }, { c: 1, d: 2 })

Options

Strict key ordering

name: strict_key_order (default: false)

expected = { a: 1, b: 2 }
actual   = { b: 2, a: 1 }

comparison = StructureCompare::StructureComparison.new(strict_key_order: false)
comparison.structures_are_equal?(expected, actual)
# => true

Value checking

name: check_values (default: true)

expected = { a: 1, b: { c: 1 } }
actual   = { a: 8, b: { c: 8 } }

comparison = StructureCompare::StructureComparison.new
comparison.structures_are_equal?(expected, actual)
# => false

comparison = StructureCompare::StructureComparison.new(check_values: false)
comparison.structures_are_equal?(expected, actual)
# => true

Indifferent Access

Hash symbol keys are treated as equal to string keys

NOTE: an exception will be raised if there's a key present as symbol and string

name: indifferent_access (default: false)

expected = { a: 1 }
actual   = { "a" => 1 }

comparison = StructureCompare::StructureComparison.new
comparison.structures_are_equal?(expected, actual)
# => false

comparison = StructureCompare::StructureComparison.new(indifferent_access: true)
comparison.structures_are_equal?(expected, actual)
# => true

hash = { a: 1, "a" => 2 }
comparison = StructureCompare::StructureComparison.new(indifferent_access: true)
comparison.structures_are_equal?(hash, hash)
# => StructureCompare::IndifferentAccessError

Float tolerance

When dealing with floats, you will want to introduce a tolerance.

NOTE: Float::EPSILON is always used for comparing Float type values.

NOTE: The check_values option must be set.

name: float_tolerance_factor (default: 0)

tolerance = +- (expected * (1.0 + tolerance_factor) + Float::EPSILON)

This means a float_tolerance_factor setting of 0.01 means that actual can be 1% different from expected to still be treated equal.

expected = { a: 10.0 }
actual_1 = { a: 10.1 }
actual_2 = { a: 10.11 }

# 1% tolerance factor
comparison = StructureCompare::StructureComparison.new(
  float_tolerance_factor: 0.01, check_values: true
)
comparison.structures_are_equal?(expected, actual_1)
# => true

comparison.structures_are_equal?(expected, actual_2)
# => false

TODOS

RSpec helpers. Refactoring.

Contributing

Fork me and send me a pull request with your feature and working tests, or just request a feature.

License

MIT License, see LICENSE file in the root directory