Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Added matchers for Hashes and JSON (which uses the Hash matcher) #79

Closed
wants to merge 38 commits into from

2 participants

@playupchris

The image belows shows an example diff, where:

  • red = items that were expected but were missing
  • magenta = items that were additional and unexpected
  • green = items that matched against a regular expression (eg.timestamps, or urls with ids in them)
  • blue = items that match a class (eg. "flames".is_a? String)
  • cyan = items that match a proc (eg. lambda { |x| %w(SHA FLA).include? x }.call("FLA") )

screenshot

@dchelimsky
Owner

We definitely need a better diff for matching hashes, but this relies on a terminal with color, which happens a lot, but far from all the time (think CI servers, etc). So we'd need a diff that doesn't rely on color either instead of or in addition to the color diff.

There are some more specific issues that I'll comment on directly in the diff.

lib/rspec/matchers/be_json_equal.rb
@@ -0,0 +1,13 @@
+require 'json'
+
+RSpec::Matchers.define :be_json_equal do |expected|
+ match do |actual|
+ @actual = JSON.parse(actual)
+ @difference = Diff.diff(@actual, expected)
+ @difference.match?
+ end
+
+ failure_message_for_should do
+ ENV['GET_FAILURE_MESSAGE'] ? @difference.to_s.inspect: @difference.to_s
@dchelimsky Owner

Why do we need this ENV var? I'd prefer to get rid of it.

It's not needed and we can get rid of it.
It was there to make it easier to create the failure messages for test cases.
I'll take it out and just toggle that on and off using a patch I could apply and un-apply when we need to make a new test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/rspec/matchers/match_hash.rb
((6 lines not shown))
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @difference = Diff.diff(actual, @expected)
+ @difference.match?
+ end
+
+ def failure_message_for_should
+ return @difference.to_s.inspect if ENV['GET_FAILURE_MESSAGE']
+ @difference.to_s
+ end
+ end
+
+ OperatorMatcher.register(Hash, '=~', RSpec::Matchers::MatchHash)
@dchelimsky Owner

I think I'd prefer to have a named matcher in this case, but I'm convincible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/rspec/matchers/be_json_equal.rb
@@ -0,0 +1,13 @@
+require 'json'
+
+RSpec::Matchers.define :be_json_equal do |expected|
+ match do |actual|
+ @actual = JSON.parse(actual)
@dchelimsky Owner

This should support Strings (as it already does), and Hashes and Arrays, so that users can pass it the result of an HTTP response (for example) or a Ruby Hash or Array directly.

What happens if users try:

actual = [1,2]
expected = [1,2]
actual.should be_json_equal(expected)

I would assume that is a failure, because, even though expected is an array, the actual is not a JSON String.
ie. If actual is assumed to be_json it should at least be a JSON String.

(If users wanted to match a Ruby Hash or Ruby Array they could use the =~ operator.)
(I would have liked to have used the =~ operator for JSON but JSON strings are of class String, so we can't register
a JSON matcher for them.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/rspec/matchers/be_json_equal.rb
@@ -0,0 +1,13 @@
+require 'json'
+
+RSpec::Matchers.define :be_json_equal do |expected|
@dchelimsky Owner

If we're going to add this I'd like it to support partial matches as well. Something like:

actual.should be_hash_including(expected) # partial match
actual.should be_hash_matching(expected) # exact match

actual.should be_json_including(expected) # partial match
actual.should be_json_matching(expected) # exact match

Of course, once we have that, we probably want the same for Arrays:

actual.should be_array_including(expected) # partial match
actual.should be_array_matching(expected) # exact match
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@playupchris

As for, "need a diff that doesn't rely on color":
How about:

{
  "x" => {
    "a" => "ABC",
    "c" => ~ "CBC",
    "b" => - /[A-Z]{3}/+ "bbc"
  }
}

Where:

  • ~ "CBC" = a regex match (previously yellow)
  • - /[A-Z]{3}/ = a missing item (previously red)
  • + "bbc" = an extra item (previously green)
@playupchris

Hi again David,
I've implemented most of your suggestions.
What do you think now?

@playupchris

ping

@dchelimsky
Owner

@playupchris - I generally like this and want to include it, but I haven't had much spare time lately and this is the largest pull request I've encountered in a long while.

Just perusing the diff again, I think there are too many options here and users would find them confusing. I think we should only have four:

string.should be_json_matching(....) # partial match all
string.should be_json_matching_exactly(....) # exact match all

hash.should be_hash_matching(....) # partial match all
hash.should be_hash_matching_exactly(....) # exact match all

I'm also unsure as to whether we should include the operator matcher.

WDYT?

@myronmarston, @justinko - you guys want to weigh in on this?

@playupchris

@dchelimsky thanks for taking the time to look at this again.

I initially thought the operator matcher was useful as one already exists for arrays (although our matcher also matches arrays), but in the array matcher:

[1,2,3].should   =~ [2,3,1]   # => would pass

Whereas in our differ that same test would fail.

So I'm thinking now that maybe the operator is confusing, as well as not being backwardly compatible (for arrays).

I think (we both agree that) the json_matchers should mirror the hash_matcher (or not exist at all; and we just expect users to convert json to hash before doing the match).

As for the other matchers, I guess they are a little confusing.
To give a brief explanation:

be_hash_matching : expected and actual has the same keys and has matching values
be_hash_all_matching : as above, but the diff shows all matching values, not just the differences
be_hash_partially_matching : expected has a subset of actual's keys and has matching values
be_hash_all_partially_matching : as above, but the diff shows all matching values, not just the differences

I guess we could drop the alls, but they're kinda handy when you want to see exactly what was matched. On the other hand we could drop the non-alls, but they're also handy when you want to cut out all the noise from the diffs. In practice, the way we've been using it is to use the all or non-all matcher depending on a DIFF_SHOW_ALL environment variable. And we toggle the env variable depending on how closely we need to look at the error messages to be able to fix our code. So I'd be more than happy to drop the alls if we could get a more detailed error message by setting an env variable.

@playupchris

Here are some examples of the 4 matchers...

pat_datetime = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/
expected = {
  "href"=>Regexp.compile("#{Regexp.quote '/api/goals/games/'}[0-9]+#{Regexp.quote '/matches/'}[0-9]+$"),
  "scheduled_start"=>pat_datetime,
  "networks"=>%w(abc nbc cnn),
  "expected_key"=>"expected_value",
  "end_date"=>pat_datetime,
  "home_team"=>{
    "name"=>String,
    "short_name"=>lambda { |x| x.is_a?(String) and x.size == 3 },
    "link"=>[{"href"=>"http://puge.example.org/api/goals/teams/FLA/players", "rel"=>"players"}],
    "href"=>"http://puge.example.org/api/goals/teams/FLA"
  },
  "away_team"=>{
    "name"=>"sharks",
    "short_name"=>"SHA",
    "link"=>[{"href"=>"http://puge.example.org/api/goals/teams/SHA/players", "rel"=>"players"}],
    "href"=>"http://puge.example.org/api/goals/teams/SHA"
  }
}
actual = {
  "href"=>"http://puge.example.org/api/goals/games/635/matches/832",
  "scheduled_start"=>"2010-01-01T00:00:00Z",
  "networks"=>["abc", "cnn", "yyy", "zzz"],
  "unexpected_key"=>"unexpected_value",
  "end_date"=>"2010-01-01T01:00:00Z",
  "home_team"=>{
    "name"=>"flames",
    "short_name"=>"FLA",
    "link"=>[{"href"=>"http://puge.example.org/api/goals/teams/FLA/players", "rel"=>"players"}],
    "href"=>"http://puge.example.org/api/goals/teams/FLA"
  },
  "away_team"=>{
    "name"=>"sharks",
    "short_name"=>"unexpected2",
    "link"=>[{"href"=>"http://puge.example.org/api/goals/teams/SHA/players", "rel"=>"players"}],
    "href"=>"http://puge.example.org/api/goals/teams/SHA"
  }
}

be_hash_matching : expected and actual has the same keys and has matching values

> RSpec::Matchers::Differ::Difference.new(expected, actual).details
{
  "networks"=>[
    - "nbc"+ "cnn",
    - "cnn"+ "yyy",
  + "zzz"
  ],
  "away_team"=>{
    "short_name"=>- "SHA"+ "unexpected2"
  },
- "expected_key"=>"expected_value",
+ "unexpected_key"=>"unexpected_value"
}
Where, - 4 missing, + 5 additional

be_hash_all_matching : as above, but the diff shows all matching values, not just the differences

> RSpec::Matchers::Differ::Difference.new(expected, actual, :show_all=>true).details`
{
  "href"=>~ http://puge.example.org(/api/goals/games/635/matches/832),
  "scheduled_start"=>~ (2010-01-01T00:00:00Z),
  "networks"=>[
    - "nbc"+ "cnn",
    - "cnn"+ "yyy",
  + "zzz"
  ],
  "end_date"=>~ (2010-01-01T01:00:00Z),
  "home_team"=>{
    "name"=>: flames,
    "short_name"=>{ FLA
  },
  "away_team"=>{
    "short_name"=>- "SHA"+ "unexpected2"
  },
- "expected_key"=>"expected_value",
+ "unexpected_key"=>"unexpected_value"
}
Where, - 4 missing, + 5 additional, ~ 3 match_regexp, : 1 match_class, { 1 match_proc

be_hash_partially_matching : expected has a subset of actual's keys and has matching values

RSpec::Matchers::Differ::PartialDifference.new(expected, actual).details
{
  "networks"=>[
    - "nbc"+ "cnn",
    - "cnn"+ "yyy"
  ],
  "away_team"=>{
    "short_name"=>- "SHA"+ "unexpected2"
  },
- "expected_key"=>"expected_value"
}
Where, - 4 missing, + 3 additional

be_hash_all_partially_matching : as above, but the diff shows all matching values, not just the differences

RSpec::Matchers::Differ::PartialDifference.new(expected, actual, :show_all=>true).details
{
  "href"=>~ http://puge.example.org(/api/goals/games/635/matches/832),
  "scheduled_start"=>~ (2010-01-01T00:00:00Z),
  "networks"=>[
    - "nbc"+ "cnn",
    - "cnn"+ "yyy",
  + "zzz"
  ],
  "end_date"=>~ (2010-01-01T01:00:00Z),
  "home_team"=>{
    "name"=>: flames,
    "short_name"=>{ FLA
  },
  "away_team"=>{
    "short_name"=>- "SHA"+ "unexpected2"
  },
- "expected_key"=>"expected_value",
+ "unexpected_key"=>"unexpected_value"
}
Where, - 4 missing, + 5 additional, ~ 3 match_regexp, : 1 match_class, { 1 match_proc

(NB. "matched values" means eg. foo matches String. or "foo" matches /[a-z]{3}/ and doesn't include equivalent values eg. "foo" matches "foo".)

@dchelimsky
Owner

So whether "all" is part of the name effects the output, but not the success/failure of the matcher. I think the env var is one good option. Another would be to add an additional argument to the matcher:

actual.should be_hash_including(expected, :show_all => true)

Or perhaps go the other way, so the norm is the show all, but for larger structures you can constrain it:

actual.should be_hash_including(expected, :condense_output => true)

I don't like :condense_output specifically, but you get the idea.

Thoughts?

@playupchris

I like that.

We'd end up setting the constraining argument with our own env variable, but doing it this way is more flexible.

How bout :verbose => false as the default?
(Or :quiet => true ... but personally I prefer :verbose => false)

@playupchris

Requests have been made to swap the green and yellow colours (because "green" currently means "additional" items, whereas people are used to "green" meaning "good").

@playupchris

I've split the Difference class off into it's own gem diff_matcher and changed the be_json_matching and be_hash_matching matchers to take the optional arguments as previously discussed.

@playupchris
  • changed over matchers from dsl to class
  • renamed be_hash_matching to be_matching as it actually matches more than just hashes.
  • removed :verbose option from diff_matcher and made it the default (:quiet still exists)
@dchelimsky
Owner

Hey @playupchris - thanks for all your hard work on this, and for extracting the diff out to a separate gem. Apologies if you did that due to this taking so long, but I think it's actually a good thing in the end. Once I saw all the differs you mention in the README, I thought a better direction for rspec-expectations would be to add better support for choosing your own differ. We had that in rspec-1, and I didn't include it in rspec-2 (which was a complete rewrite), but I think it would work well now. Rather than merging this pull, I'm going to add support for choosing your own differ to the next release (2.9 - 2.8 is already out as a release candidate and I don't want this to hold that up from going final).

I've created a new issue (#97) for this, and welcome your input on that issue.

Cheers,
David

@dchelimsky dchelimsky closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 22, 2011
  1. @playupchris
  2. @playupchris
  3. @playupchris
Commits on Jun 23, 2011
  1. @playupchris
  2. @playupchris
  3. @playupchris

    Fix to failure messages.

    playupchris authored
Commits on Jun 24, 2011
  1. @playupchris

    Partial matches on hashes are matches that don't have any missing ite…

    playupchris authored
    …ms and don't contain any other hashes that have missing items.
  2. @playupchris
Commits on Jun 25, 2011
  1. @playupchris

    Refactoring

    playupchris authored
  2. @playupchris
Commits on Jun 26, 2011
  1. @playupchris
  2. @playupchris
  3. @playupchris
  4. @playupchris
  5. @playupchris
  6. @playupchris
  7. @playupchris

    remove comment

    playupchris authored
Commits on Jun 27, 2011
  1. @playupchris
  2. @playupchris
Commits on Jun 28, 2011
  1. @playupchris
  2. @playupchris

    Fix to be_json spec

    playupchris authored
Commits on Jun 29, 2011
  1. @playupchris

    Added in a proc matcher

    playupchris authored
  2. @playupchris

    Added in a class matcher

    playupchris authored
Commits on Jul 3, 2011
  1. @playupchris

    Added summaries to match strings which now show the number of regex m…

    playupchris authored
    …atches, class matches and proc matches
  2. @playupchris
Commits on Jul 4, 2011
  1. @playupchris

    updated spec

    playupchris authored
  2. @playupchris
  3. @playupchris
Commits on Jul 5, 2011
  1. @playupchris

    Nasty hack to get partial match working for arrays that contain hashe…

    playupchris authored
    …s that have additional values.
  2. @playupchris

    Refactored shared examples. Changed diff's 'summary' to 'details' and…

    playupchris authored
    … added a 'show' which can show all items types or just :incorrect, :missing and or :additional item types.
  3. @playupchris

    Added DEFAULT_ITEM_TYPES

    playupchris authored
Commits on Jul 6, 2011
  1. @playupchris
Commits on Jul 24, 2011
  1. @playupchris
  2. @playupchris
Commits on Aug 9, 2011
  1. @playupchris
Commits on Sep 16, 2011
  1. @playupchris
  2. @playupchris
  3. @playupchris
This page is out of date. Refresh to see the latest.
View
2  lib/rspec/matchers.rb
@@ -181,6 +181,7 @@ module Matchers
require 'rspec/matchers/be'
require 'rspec/matchers/be_close'
require 'rspec/matchers/be_instance_of'
+require 'rspec/matchers/be_json'
require 'rspec/matchers/be_kind_of'
require 'rspec/matchers/be_within'
require 'rspec/matchers/block_aliases'
@@ -197,6 +198,7 @@ module Matchers
require 'rspec/matchers/include'
require 'rspec/matchers/match'
require 'rspec/matchers/match_array'
+require 'rspec/matchers/match_hash'
require 'rspec/matchers/method_missing'
require 'rspec/matchers/raise_error'
require 'rspec/matchers/respond_to'
View
32 lib/rspec/matchers/be_json.rb
@@ -0,0 +1,32 @@
+require 'diff_matcher'
+require 'json'
+
+RSpec::Matchers.define :be_json_matching do |expected|
+ match do |actual|
+ @actual = JSON.parse(actual)
+ @difference = DiffMatcher::Difference.new(expected, @actual)
+ @difference.matches?
+ end
+
+ failure_message_for_should do
+ @difference.details
+ end
+end
+
+module RSpec
+ module Matchers
+ def be_json_matching(expected, opts={})
+ Matcher.new :be_json_matching, expected do |_expected_|
+ match do |actual|
+ @actual = JSON.parse(actual)
+ @difference = DiffMatcher::Difference.new(_expected_, @actual, opts)
+ @difference.matching?
+ end
+
+ failure_message_for_should do
+ @difference.to_s
+ end
+ end
+ end
+ end
+end
View
20 lib/rspec/matchers/match_hash.rb
@@ -0,0 +1,20 @@
+require 'diff_matcher'
+
+module RSpec
+ module Matchers
+ def be_hash_matching(expected, opts={})
+ opts.update(:color_enabled=>RSpec.configuration.color_enabled?)
+
+ Matcher.new :be_hash_matching, expected do |_expected_|
+ match do |actual|
+ @difference = DiffMatcher::Difference.new(_expected_, actual, opts)
+ @difference.matching?
+ end
+
+ failure_message_for_should do
+ @difference.to_s
+ end
+ end
+ end
+ end
+end
View
1  rspec-expectations.gemspec
@@ -21,4 +21,5 @@ Gem::Specification.new do |s|
s.require_path = "lib"
s.add_runtime_dependency 'diff-lcs', '~> 1.1.2'
+ s.add_runtime_dependency 'diff_matcher','~> 1.0.0'
end
View
29 spec/rspec/matchers/be_json_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+module Rspec
+ module Matchers
+
+ shared_examples_for "a json matcher" do
+ it "passes if matches" do
+ actual.should be_json_matching expected
+ end
+
+ it "fails if doesn't match" do
+ lambda { failing.should be_json_matching(expected, :color_scheme=>:default) }.should fail_with failure_message
+ end
+ end
+
+ describe "actual.should be_json_matching(expected)" do
+ context "a json string matched against a hash with a regex" do
+ let(:expected ) { { 'x' => {'a' => /[A-Z]{3}/ } } }
+ let(:actual ) { '{ "x" : {"a" : "ABC" } }' }
+ let(:failing ) { '{ "x" : {"a" : "123" } }' }
+ let(:failure_message) {
+ "\e[0m{\n\e[0m \"x\"=>{\n\e[0m \"a\"=>\e[31m- \e[1m/[A-Z]{3}/\e[0m\e[33m+ \e[1m\"123\"\e[0m\n\e[0m }\n\e[0m}\nWhere, \e[31m- \e[1m1 missing\e[0m, \e[33m+ \e[1m1 additional\e[0m"
+ }
+
+ it_should_behave_like "a json matcher"
+ end
+ end
+ end
+end
View
171 spec/rspec/matchers/match_hash_spec.rb
@@ -0,0 +1,171 @@
+require 'spec_helper'
+
+def fix_eof_problem(s)
+ # <<-EOF isn't working like its meant to :(
+ whitespace = s.split("\n")[-1][/^[ ]+/]
+ indentation = whitespace ? whitespace.size : 0
+ s.gsub("\n#{" " * indentation}", "\n").strip
+end
+
+module RSpec
+ module Matchers
+ describe :be_hash_matching do
+
+ shared_examples_for "a hash matcher" do
+ it "passes if matches" do
+ same.should be_hash_matching(expected, opts)
+ end
+
+ it "fails if doesn't match" do
+ failure_message = fix_eof_problem(difference)
+ DiffMatcher::Difference.new(expected, different, opts).to_s.should == failure_message
+ lambda { different.should be_hash_matching(expected, opts) }.should fail_with failure_message
+ end
+ end
+
+ context "when expected has multiple items," do
+ let(:expected ) { { :a=>{ :a1=>11 }, :b=>[ 21, 22 ], :c=>/\d/, :d=>Fixnum, :e=>lambda { |x| (4..6).include? x } } }
+ let(:same ) { { :a=>{ :a1=>11 }, :b=>[ 21, 22 ], :c=>'3' , :d=>4 , :e=>5 } }
+ let(:different ) { { :a=>{ :a1=>10, :a2=>12 }, :b=>[ 21 ], :c=>'3' , :d=>4 , :e=>5 } }
+
+ before { RSpec.configuration.should_receive(:color_enabled?).and_return false }
+
+ describe "it shows regex, class, proc matches and" do
+ let(:opts) { {} }
+ let(:difference) {
+ <<-EOF
+ {
+ :a=>{
+ :a1=>- 11+ 10,
+ + :a2=>12
+ },
+ :b=>[
+ - 22
+ ],
+ :c=>~ (3),
+ :d=>: 4,
+ :e=>{ 5
+ }
+ Where, - 2 missing, + 2 additional, ~ 1 match_regexp, : 1 match_class, { 1 match_proc
+ EOF
+ }
+
+ it_should_behave_like "a hash matcher"
+ end
+
+ describe "it doesn't show matches and" do
+ let(:opts) { { :quiet=>true } }
+ let(:difference) {
+ <<-EOF
+ {
+ :a=>{
+ :a1=>- 11+ 10,
+ + :a2=>12
+ },
+ :b=>[
+ - 22
+ ]
+ }
+ Where, - 2 missing, + 2 additional
+ EOF
+ }
+
+ it_should_behave_like "a hash matcher"
+ end
+
+ describe "it ignores additional values" do
+ let(:opts) { { :ignore_additional=>true, :quiet=>true } }
+ let(:same ) { { :a=>{ :a1=>11, :xxxx=>9999 }, :b=>[ 21, 22, 9999 ], :c=>'3', :d=>4, :e=>5 } }
+ let(:different ) { { :a=>{ :a1=>10, :xxxx=>9999 }, :b=>[ 21 ], :c=>'3', :d=>4, :e=>5 } }
+ let(:difference) {
+ <<-EOF
+ {
+ :a=>{
+ :a1=>- 11+ 10
+ },
+ :b=>[
+ - 22
+ ]
+ }
+ Where, - 2 missing, + 1 additional
+ EOF
+ }
+
+ it_should_behave_like "a hash matcher"
+ end
+
+ describe "it shows all matches and" do
+ let(:opts) { { :verbose=>true } }
+ let(:difference) {
+ <<-EOF
+ {
+ :a=>{
+ :a1=>- 11+ 10,
+ + :a2=>12
+ },
+ :b=>[
+ 21,
+ - 22
+ ],
+ :c=>~ (3),
+ :d=>: 4,
+ :e=>{ 5
+ }
+ Where, - 2 missing, + 2 additional, ~ 1 match_regexp, : 1 match_class, { 1 match_proc
+ EOF
+ }
+
+ it_should_behave_like "a hash matcher"
+ end
+
+ describe "it shows matches in color and" do
+ let(:opts) { { :verbose=>true, :color_scheme=>:default } }
+ let(:difference) {
+ <<-EOF
+ \e[0m{
+ \e[0m :a=>{
+ \e[0m :a1=>\e[31m- \e[1m11\e[0m\e[33m+ \e[1m10\e[0m,
+ \e[0m \e[33m+ \e[1m:a2=>12\e[0m
+ \e[0m },
+ \e[0m :b=>[
+ \e[0m 21,
+ \e[0m \e[31m- \e[1m22\e[0m
+ \e[0m ],
+ \e[0m :c=>\e[32m~ \e[0m\e[32m(\e[1m3\e[0m\e[32m)\e[0m\e[0m,
+ \e[0m :d=>\e[34m: \e[1m4\e[0m,
+ \e[0m :e=>\e[36m{ \e[1m5\e[0m
+ \e[0m}
+ Where, \e[31m- \e[1m2 missing\e[0m, \e[33m+ \e[1m2 additional\e[0m, \e[32m~ \e[1m1 match_regexp\e[0m, \e[34m: \e[1m1 match_class\e[0m, \e[36m{ \e[1m1 match_proc\e[0m
+ EOF
+ }
+
+ it_should_behave_like "a hash matcher"
+
+ context "on a white background" do
+ let(:opts) { { :verbose=>true, :color_scheme=>:white_background } }
+ let(:difference) {
+ <<-EOF
+ \e[0m{
+ \e[0m :a=>{
+ \e[0m :a1=>\e[31m- \e[1m11\e[0m\e[35m+ \e[1m10\e[0m,
+ \e[0m \e[35m+ \e[1m:a2=>12\e[0m
+ \e[0m },
+ \e[0m :b=>[
+ \e[0m 21,
+ \e[0m \e[31m- \e[1m22\e[0m
+ \e[0m ],
+ \e[0m :c=>\e[32m~ \e[0m\e[32m(\e[1m3\e[0m\e[32m)\e[0m\e[0m,
+ \e[0m :d=>\e[34m: \e[1m4\e[0m,
+ \e[0m :e=>\e[36m{ \e[1m5\e[0m
+ \e[0m}
+ Where, \e[31m- \e[1m2 missing\e[0m, \e[35m+ \e[1m2 additional\e[0m, \e[32m~ \e[1m1 match_regexp\e[0m, \e[34m: \e[1m1 match_class\e[0m, \e[36m{ \e[1m1 match_proc\e[0m
+ EOF
+ }
+
+ it_should_behave_like "a hash matcher"
+ end
+ end
+ end
+ end
+ end
+end
Something went wrong with that request. Please try again.