Skip to content

Commit

Permalink
Merge pull request #1455 from nevinera/nev--1132--handle-dynamic-matc…
Browse files Browse the repository at this point in the history
…hes-on-mocks-specially

Handle dynamic mocks specially for null-object doubles
  • Loading branch information
JonRowe committed Jun 15, 2024
1 parent ca8b80c commit 069391f
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 2 deletions.
29 changes: 27 additions & 2 deletions lib/rspec/matchers/built_in/has.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,25 @@ def description

private

# Catch a semi-frequent typo - if you have strict_predicate_matchers disabled and
# expect(spy).to have_receieveddd(:foo) it would be evergreen - the dynamic matcher
# queries `has_receiveddd?`, the spy _fakes_ the method, returning its (truthy) self.
if defined?(RSpec::Mocks::Double)
def really_responds_to?(method)
if RSpec::Mocks::Double === @actual
@actual.respond_to?(method) && methods_include?(method)
else
@actual.respond_to?(method)
end
end
else
def really_responds_to?(method)
@actual.respond_to?(method)
end
end

def predicate_accessible?
@actual.respond_to? predicate
really_responds_to?(predicate)
end

# support 1.8.7, evaluate once at load time for performance
Expand All @@ -56,11 +73,19 @@ def predicate_accessible?
def private_predicate?
@actual.private_methods.include? predicate.to_s
end

def methods_include?(method)
@actual.methods.include?(method.to_s)
end
# :nocov:
else
def private_predicate?
@actual.private_methods.include? predicate
end

def methods_include?(method)
@actual.methods.include?(method)
end
end

def predicate_result
Expand Down Expand Up @@ -155,7 +180,7 @@ def failure_to_respond_explanation
end

def predicate_accessible?
super || actual.respond_to?(present_tense_predicate)
super || really_responds_to?(present_tense_predicate)
end

def present_tense_predicate
Expand Down
14 changes: 14 additions & 0 deletions spec/rspec/matchers/built_in/be_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@
expect(actual).to be_happy
}.to fail_with("expected `#{actual.inspect}.happy?` to be truthy, got false")
end

it "allows dynamic matchers to pass for supplied methods on spies" do
thing = spy("thing", :has_recv? => true)
expect(thing).to have_recv(:foo)
expect(thing).to have_received(:has_recv?).with(:foo)
end

it "does not allow dynamic matchers to pass for inferred methods on spies" do
thing = spy("thing")
expect {
expect(thing).to have_recv(:foo)
}.to fail
expect(thing).not_to have_received(:has_recv?)
end
end

it "fails when actual does not respond to :predicate?" do
Expand Down
14 changes: 14 additions & 0 deletions spec/rspec/matchers/built_in/has_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,20 @@ def o.has_sym?(sym); sym == :foo; end
actual = double("actual", :has_foo? => nil)
expect(actual).not_to have_foo
end

it "allows dynamic matchers to pass for supplied methods on spies" do
thing = spy("thing", :furg? => true)
expect(thing).to be_furg(:foo)
expect(thing).to have_received(:furg?).with(:foo)
end

it "does not allow dynamic matchers to pass for inferred methods on spies" do
thing = spy("thing")
expect {
expect(thing).to be_furg(:foo)
}.to fail
expect(thing).not_to have_received(:furg?)
end
end

it "fails if #has_sym?(*args) returns true" do
Expand Down

0 comments on commit 069391f

Please sign in to comment.