Generates a diff by matching against expected values, classes, regexes and/or procs.
DiffMatcher performs recursive matches on values contained in hashes, arrays and combinations thereof.
Values in a containing object match when:
actual.is_a? expected # when expected is a class
expected.match actual # when expected is a regexp
expected.call actual # when expected is a proc
actual == expected # when expected is anything elseExample:
puts DiffMatcher::difference(
{ :a=>{ :a1=>11 }, :b=>[ 21, 22 ], :c=>/\d/, :d=>Fixnum, :e=>lambda { |x| (4..6).include? x } },
{ :a=>{ :a1=>10, :a2=>12 }, :b=>[ 21 ], :c=>'3' , :d=>4 , :e=>5 },
:color_scheme=>:white_background
)gem install diff_matcher
require 'diff_matcher'
DiffMatcher::difference(actual, expected, opts={})When expected != actual
puts DiffMatcher::difference(1, 2)
# => - 1+ 2
# => Where, - 1 missing, + 1 additionalWhen expected == actual
p DiffMatcher::difference(1, 1)
# => nilWhen actual is an instance of the expected
p DiffMatcher::difference(String, '1')
# => nilWhen actual is a string that matches the expected regex
p DiffMatcher::difference(/[a-z]/, "a")
# => nilWhen actual is passed to an expected proc and it returns true
is_boolean = lambda { |x| [FalseClass, TrueClass].include? x.class }
p DiffMatcher::difference(is_boolean, true)
# => nilWhen actual is missing one of the expected values
puts DiffMatcher::difference([1, 2], [1])
# => [
# => 1
# => - 2
# => ]
# => Where, - 1 missingWhen actual has additional values to the expected
puts DiffMatcher::difference([1], [1, 2])
# => [
# => 1
# => + 2
# => ]
# => Where, + 1 additionalWhen expected is a Hash with optional keys use a Matcher.
puts DiffMatcher::difference(
DiffMatcher::Matcher.new({:name=>String, :age=>Fixnum}, :optional_keys=>[:age]),
{:name=>0}
)
{
:name=>- String+ 0
}
Where, - 1 missing, + 1 additionalWhen expected can take multiple forms use some Matchers ||ed together.
puts DiffMatcher::difference(DiffMatcher::Matcher.new(Fixnum) || DiffMatcher.new(Float), "3")
- Float+ "3"
Where, - 1 missing, + 1 additional(NB. DiffMatcher::Matcher[Fixnum, Float] can be used as a shortcut for
DiffMatcher::Matcher.new(Fixnum) || DiffMatcher.new(Float)
)
When actual is an array of unknown size use an AllMatcher to match
against all the elements in the array.
puts DiffMatcher::difference(DiffMatcher::AllMatcher.new(Fixnum), [1, 2, "3"])
[
: 1,
: 2,
- Fixnum+ "3"
]
Where, - 1 missing, + 1 additional, : 2 match_classWhen actual is an array with a limited size use an AllMatcher to match
against all the elements in the array adhering to the limits of :min
and or :max.
puts DiffMatcher::difference(DiffMatcher::AllMatcher.new(Fixnum, :min=>3), [1, 2])
[
: 1,
: 2,
- Fixnum
]
Where, - 1 missing, : 2 match_classWhen actual is an array of unknown size and expected can take
multiple forms use a Matcher inside of an AllMatcher to match
against all the elements in the array in any of the forms.
puts DiffMatcher::difference(
DiffMatcher::AllMatcher.new(
DiffMatcher::Matcher[Fixnum, Float]
),
[1, 2.00, "3"]
)
[
| 1,
| 2.0,
- Float+ "3"
]
Where, - 1 missing, + 1 additional, | 2 match_matcher:ignore_additional=>true will match even if actual has additional items
p DiffMatcher::difference([1], [1, 2], :ignore_additional=>true)
# => nil:quiet=>true shows only missing and additional items in the output
puts DiffMatcher::difference([Fixnum, 2], [1], :quiet=>true)
# => [
# => - 2
# => ]
# => Where, - 1 missingThe items shown in a difference are prefixed as follows:
missing => "- "
additional => "+ "
match value =>
match regexp => "~ "
match class => ": "
match matcher => "| "
match proc => ". "
match proc => "{ "
Colours (defined in colour schemes) can also appear in the difference.
Using the :default colour scheme items shown in a difference are coloured as follows:
missing => red
additional => yellow
match value =>
match regexp => green
match class => blue
match matcher => blue
match range => cyan
match proc => cyan
Other colour schemes, eg. :color_scheme=>:white_background will use different colour mappings.
- http://difflcs.rubyforge.org (A resonably fast diff algorithm using longest common substrings)
- http://github.com/samg/diffy (Provides a convenient interfaces to Unix diff)
- http://github.com/pvande/differ (A simple gem for generating string diffs)
- http://github.com/shuber/sub_diff (Apply regular expression replacements to strings while presenting the result in a “diff” like format)
- http://github.com/rattle/diffrenderer (Takes two pieces of source text/html and creates a neato html diff output)
- http://github.com/tinogomes/ssdiff (Super Stupid Diff)
- http://github.com/postmodern/tdiff (Calculates the differences between two tree-like structures)
- http://github.com/Blargel/easy_diff (Recursive diff, merge, and unmerge for hashes and arrays)
This gem came about because rspec doesn't have a decent differ for matching hashes and/or JSON.
It started out as a pull request, to be implemented as a
be_hash_matching rspec matcher,
but seemed useful enough to be its own stand alone gem.
Out of the similar gems above, easy_diff looks like a good alternative to this gem. It has extra functionality in also being able to recursively merge hashes and arrays. sub_diff can use regular expressions in its match and subsequent diff
DiffMatcher can match using not only regexes but classes and procs. And the difference string that it outputs can be formatted in several ways as needed.
To use with rspec create the following custom matcher:
require 'diff_matcher'
module RSpec
module Matchers
class BeMatching
include BaseMatcher
def initialize(expected, opts)
@expected = expected
@opts = opts.update(:color_enabled=>RSpec::configuration.color_enabled?)
end
def matches?(actual)
@difference = DiffMatcher::Difference.new(expected, actual, @opts)
@difference.matching?
end
def failure_message_for_should
@difference.to_s
end
end
def be_matching(expected, opts={})
Matchers::BeMatching.new(expected, opts)
end
end
endAnd use it with:
describe "hash matcher" do
subject { { :a=>1, :b=>2, :c=>'3', :d=>4, :e=>"additional stuff" } }
let(:expected) { { :a=>1, :b=>Fixnum, :c=>/[0-9]/, :d=>lambda { |x| (3..5).include?(x) } } }
it { should be_matching(expected, :ignore_additional=>true) }
it { should be_matching(expected) }
endWill result in:
Failures:
1) hash matcher
Failure/Error: it { should be_matching(expected) }
{
:a=>1,
:b=>: 2,
:c=>~ (3),
:d=>{ 4,
+ :e=>"additional stuff"
}
Where, + 1 additional, ~ 1 match_regexp, : 1 match_class, { 1 match_proc
# ./hash_matcher_spec.rb:6:in `block (2 levels) in <top (required)>'
Finished in 0.00601 seconds
2 examples, 1 failure
Fork, write some tests and send a pull request (bonus points for topic branches).
Our company is using this gem to test our JSON API which has got it to a stable v1.0.0 release.
There's a pull request to use this gem in a be_hash_matching
rspec matcher.


