Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit a0c3958

Browse files
pablobmmyronmarston
authored andcommitted
Duck-typing support for the include matcher (#1012)
* If it quacks like a Hash, treat it like a Hash * DRY * Not loaded by default in old Rubies * Trying to get the build passing... * No need to put this on Relish * Not much value to pseudo-hashes allowed as expecteds
1 parent 5293ded commit a0c3958

File tree

2 files changed

+57
-37
lines changed

2 files changed

+57
-37
lines changed

lib/rspec/matchers/built_in/include.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ def initialize(*expecteds)
1515
# @api private
1616
# @return [Boolean]
1717
def matches?(actual)
18+
actual = actual.to_hash if actual.respond_to?(:to_hash)
1819
perform_match(actual) { |v| v }
1920
end
2021

2122
# @api private
2223
# @return [Boolean]
2324
def does_not_match?(actual)
25+
actual = actual.to_hash if actual.respond_to?(:to_hash)
2426
perform_match(actual) { |v| !v }
2527
end
2628

spec/rspec/matchers/built_in/include_spec.rb

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,48 @@
33
expect(include("a")).to be_diffable
44
end
55

6+
shared_examples_for "a Hash target" do
7+
def build_target(hsh)
8+
hsh
9+
end
10+
11+
it 'passes if target has the expected as a key' do
12+
expect(build_target(:key => 'value')).to include(:key)
13+
end
14+
15+
it "fails if target does not include expected" do
16+
expect {
17+
expect(build_target(:key => 'value')).to include(:other)
18+
}.to fail_matching(%Q|expected {:key => "value"} to include :other|)
19+
end
20+
21+
it "fails if target doesn't have a key and we expect nil" do
22+
expect {
23+
expect(build_target({})).to include(:something => nil)
24+
}.to fail_matching(%Q|expected {} to include {:something => nil}|)
25+
end
26+
27+
it 'works even when an entry in the hash overrides #send' do
28+
hash = build_target(:key => 'value')
29+
def hash.send; :sent; end
30+
expect(hash).to include(hash)
31+
end
32+
33+
it 'provides a valid diff' do
34+
allow(RSpec::Matchers.configuration).to receive(:color?).and_return(false)
35+
36+
expect {
37+
expect(build_target(:foo => 1, :bar => 2)).to include(:foo => 1, :bar => 3)
38+
}.to fail_including(dedent(<<-END))
39+
|Diff:
40+
|@@ -1,3 +1,3 @@
41+
|-:bar => 3,
42+
|+:bar => 2,
43+
| :foo => 1,
44+
END
45+
end
46+
end
47+
648
describe "expect(...).to include(with_one_arg)" do
749
it_behaves_like "an RSpec matcher", :valid_value => [1, 2], :invalid_value => [1] do
850
let(:matcher) { include(2) }
@@ -86,50 +128,26 @@
86128
end
87129

88130
context "for a hash target" do
89-
it 'passes if target has the expected as a key' do
90-
expect({:key => 'value'}).to include(:key)
91-
end
92-
93-
it "fails if target does not include expected" do
94-
expect {
95-
expect({:key => 'value'}).to include(:other)
96-
}.to fail_matching(%Q|expected {:key => "value"} to include :other|)
97-
end
98-
99-
it "fails if target doesn't have a key and we expect nil" do
100-
expect {
101-
expect({}).to include(:something => nil)
102-
}.to fail_matching(%Q|expected {} to include {:something => nil}|)
103-
end
131+
it_behaves_like "a Hash target"
132+
end
104133

105-
it 'works even when an entry in the hash overrides #send' do
106-
hash = { :key => 'value' }
107-
def hash.send; :sent; end
108-
expect(hash).to include(hash)
134+
context "for a target that can pass for a hash" do
135+
def build_target(hsh)
136+
PseudoHash.new(hsh)
109137
end
110138

111-
it 'provides a valid diff' do
112-
allow(RSpec::Matchers.configuration).to receive(:color?).and_return(false)
113-
114-
expect {
115-
expect(:foo => 1, :bar => 2).to include(:foo => 1, :bar => 3)
116-
}.to fail_including(dedent(<<-END))
117-
|Diff:
118-
|@@ -1,3 +1,3 @@
119-
|-:bar => 3,
120-
|+:bar => 2,
121-
| :foo => 1,
122-
END
123-
end
139+
around do |example|
140+
in_sub_process_if_possible do
141+
require 'delegate'
124142

125-
context 'that overrides #send' do
126-
it 'still works' do
127-
array = [1, 2]
128-
def array.send; :sent; end
143+
class PseudoHash < SimpleDelegator
144+
end
129145

130-
expect(array).to include(*array)
146+
example.run
131147
end
132148
end
149+
150+
it_behaves_like "a Hash target"
133151
end
134152
end
135153

0 commit comments

Comments
 (0)