No commit activity in last 3 years
No release in over 3 years
Compares the structure of two deep nested Ruby structures
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

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