rspec-expectations icon indicating copy to clipboard operation
rspec-expectations copied to clipboard

Handle dynamic mocks specially for null-object doubles

Open nevinera opened this issue 1 year ago • 6 comments

This hopefully resolves #1132 and #1288 - essentially, if you leave strict_predicate_matchers disabled and use a null-object spy like this:

let(:thing) { spy('my thing') }

it "does the thing right" do
  do_it
  expect(thing).to have_receivedd(:foo)
end

It'll pass. Note the typo in have_receivedd? The dynamic matcher will call has_receivedd? on the spy, the spy will return itself (that's what it does), and the matcher will note that the result is truthy, and pass the spec. That's kind of an issue - turning off that setting does solve it, but that won't be the default until rspec 4, and even then it's not the ideal behavior, just a lot better.

This PR updates the dynamic matchers to notice when their target is a Double, and change behavior slightly - for Doubles, don't just check if they respond to the method, also check if it's in the methods list. That'll suffice to confirm that it's explicitly mocked on the double, rather than being an automatic response.

nevinera avatar May 07 '24 23:05 nevinera

22 deprecation warnings total

Wondering why we don’t print them 🧐

pirj avatar May 08 '24 06:05 pirj

Can you please use our fail matcher in the specs

Thank you! This was so ugly, I should have known there would be a matcher for that :-D

nevinera avatar May 08 '24 10:05 nevinera

  1) Object#should on interpreters that have BasicObject does not break the deprecation check on BasicObject-subclassed proxy objects
     Failure/Error: @target.send(name, *args)

     NoMethodError:
       undefined method `send' for #<BasicObject:0x000000010493faf8>

.. BasicObject doesn't have is_a?, so this line breaks for those. Good test, I'd have never thought of that!

I'm not sure how to safely detect that I'm dealing with a BasicObject though, since it doesn't have respond_to? or class methods (or anything else I can think of). Incidentally, it also doesn't have a send method, so this bit here just produces a NoMethodError: undefined method send' for #BasicObject:0x000000012ca06360`, which seems probably unintended?

Any advice? I could just catch NoMethodError on really_responds_to? and assume it means we're on a BasicObject (and therefore not a spy). I'll push that up, but I feel like there ought to be a better way to do this - possibly behavior to add in the Proxy itself?

(I think the current changes pass the tests, I just don't love using error-handling for this kind of thing unless it's actually necessary)

nevinera avatar May 08 '24 11:05 nevinera

Oh, and please let me know if I'm using PRs wrong here - I've run into a variety of expectations regarding who resolves conversations, how much feedback to ask for, etc. You guys seem super-active, so I'm hoping I can be helpful.

nevinera avatar May 08 '24 11:05 nevinera

No worries on the code, it feels that we’re getting along quite good. Thanks for your tireless contributions!

pirj avatar May 09 '24 08:05 pirj

Darn, looks like I still have some kind of problem on ruby-1.8.7. I've never actually gotten it to install on an m1, guess I'll spend some time on that today. It's obviously going to keep coming up :-)

nevinera avatar May 09 '24 11:05 nevinera

Thank you!

pirj avatar Jun 16 '24 06:06 pirj

Released in 3.13.2

JonRowe avatar Aug 20 '24 20:08 JonRowe