From ec935403fbe8324dbc3bd9136edbb49c39306854 Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Fri, 27 Dec 2013 21:43:29 -0800 Subject: [PATCH] `contain_exactly` should work with sets and other collections, too. --- Changelog.md | 2 ++ lib/rspec/matchers/built_in/contain_exactly.rb | 16 ++++++++++------ .../matchers/built_in/contain_exactly_spec.rb | 17 ++++++++--------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Changelog.md b/Changelog.md index 7f6598653..8256d3964 100644 --- a/Changelog.md +++ b/Changelog.md @@ -22,6 +22,8 @@ Enhancements: Note that it expects the expected array to be splatted as individual args: `expect(array).to contain_exactly(1, 2)` is the same as `expect(array).to match_array([1, 2])`. (Myron Marston) +* Update `contain_exactly`/`match_array` so that it can match against + other non-array collections (such as a `Set`). (Myron Marston) * Update built-in matchers so that they can accept matchers as arguments to allow you to compose matchers in arbitrary ways. (Myron Marston) * Add `RSpec::Matchers::Composable` mixin that can be used to make diff --git a/lib/rspec/matchers/built_in/contain_exactly.rb b/lib/rspec/matchers/built_in/contain_exactly.rb index 916555098..e409ef7c5 100644 --- a/lib/rspec/matchers/built_in/contain_exactly.rb +++ b/lib/rspec/matchers/built_in/contain_exactly.rb @@ -3,21 +3,21 @@ module Matchers module BuiltIn class ContainExactly < BaseMatcher def match(expected, actual) - return false unless actual.respond_to? :to_ary + return false unless actual_is_a_collection? + @actual = actual.to_a extra_items.empty? && missing_items.empty? end def failure_message - if actual.respond_to? :to_ary - message = "expected collection contained: #{safe_sort(surface_descriptions_in expected).inspect}\n" + if actual_is_a_collection? + message = "expected collection contained: #{safe_sort(surface_descriptions_in expected).inspect}\n" message += "actual collection contained: #{safe_sort(actual).inspect}\n" message += "the missing elements were: #{safe_sort(surface_descriptions_in missing_items).inspect}\n" unless missing_items.empty? message += "the extra elements were: #{safe_sort(extra_items).inspect}\n" unless extra_items.empty? + message else - message = "expected an array, actual collection was #{actual.inspect}" + "expected a collection that can be converted to an array with `#to_a`, but got #{actual.inspect}" end - - message end def failure_message_when_negated @@ -30,6 +30,10 @@ def description private + def actual_is_a_collection? + enumerable?(actual) && actual.respond_to?(:to_a) + end + def safe_sort(array) array.sort rescue array end diff --git a/spec/rspec/matchers/built_in/contain_exactly_spec.rb b/spec/rspec/matchers/built_in/contain_exactly_spec.rb index 653a57811..5a9710440 100644 --- a/spec/rspec/matchers/built_in/contain_exactly_spec.rb +++ b/spec/rspec/matchers/built_in/contain_exactly_spec.rb @@ -189,27 +189,26 @@ def timeout_if_not_debugging(time) it "fails with nil and the expected error message is given" do expect { expect(nil).to contain_exactly(1, 2, 3) - }.to fail_with(/expected an array/) + }.to fail_with(/expected a collection/) end it "fails with a float and the expected error message is given" do expect { expect(3.7).to contain_exactly(1, 2, 3) - }.to fail_with(/expected an array/) + }.to fail_with(/expected a collection/) end it "fails with a string and the expected error message is given" do expect { expect("I like turtles").to contain_exactly(1, 2, 3) - }.to fail_with(/expected an array/) + }.to fail_with(/expected a collection/) end - context "when using the `should =~` syntax", :uses_should do - it 'fails with a clear message when given a hash' do - expect { - {}.should =~ {} - }.to fail_with(/expected an array/) - end + it 'works with other collection types' do + expect(Set.new([3, 2, 1])).to contain_exactly(1, 2, 3) + expect { + expect(Set.new([3, 2, 1])).to contain_exactly(1, 2) + }.to fail_matching("expected collection contained: [1, 2]") end end