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

Add argument matcher for expecting a block.

Open JonRowe opened this issue 6 years ago • 12 comments

This is @kaiwren's a_block argument matcher with fleshed out sad path specs, and a refactoring of the match logic to allow mixing with other arguments. Review please @kaiwren @ioquatix @benoittgt.

JonRowe avatar Sep 17 '18 10:09 JonRowe

Some further improvements could be made here,what happens when used positionally? and does it always mean the supplied &block argument? After all object.msg(proc {}, proc {}) is valid Ruby, should we expect on this with .with(a_block, a_block) and how do we differentiated that from msg(proc{}) { }. And if we don't allow that should we reject the method expectation out of hand? We already have and_yield for block arguments too.

JonRowe avatar Sep 17 '18 10:09 JonRowe

My only use case was to do something like expect(thing).to receive(:method).with_block, so anything that can do that I am happy with. It might even make sense to add that specific method with_block as that's what I want to use initially. I think the extended syntax is also useful.

ioquatix avatar Sep 17 '18 10:09 ioquatix

@ioquatix We already have that, it's called and_yield, e.g. expect(thing).to receive(:method).and_yield

JonRowe avatar Sep 17 '18 10:09 JonRowe

For some reason I couldn't find this initially... I did look for it!

ioquatix avatar Sep 17 '18 10:09 ioquatix

Maybe we can say this is good enough for a first step? Ready the tests I found it already a good improvement.

Good job

benoittgt avatar Sep 17 '18 13:09 benoittgt

Thanks so much!

kaiwren avatar Sep 17 '18 15:09 kaiwren

A quick question - IMO ArgumentListMatcher and associated code could use a little refactoring to better express intent, and to include blocks in the args verification in a more consistent way (that's one of the reasons I stopped at a_block). I'm happy to attempt this if it makes sense to do so, and of course, it goes without saying that I'll keep the changes small, and will completely understand if they are rejected. Would that be useful?

kaiwren avatar Sep 17 '18 18:09 kaiwren

You are welcome to have a go at refactoring it if you think you can make it better, please make sure you understand the matcher protocol before changing method names, e.g. it's important and non optional that === is used as the match method, as it's part of our composability and is what lets argument matchers and other matchers be combined.

Thinking about this, theres now ambiguity as to what we mean when, @myronmarston I'm leaning towards adding a_block to match a block "anywhere", and then adding .with_block to mean the block argument in order to be ambiguous, meaning with(...)s arguments will remain "real" argument. Effectively .with_block would just check for the presence of &block, or could check for a specific block?

JonRowe avatar Sep 18 '18 08:09 JonRowe

Procs/Lambdas as an argument and blocks are different things. I suggest consider a_block and a_proc/a_lambda/a_callable?

ioquatix avatar Sep 18 '18 10:09 ioquatix

@JonRowe yep, well noted, I will keep that in mind.

Yep agree that bound and unbound blocks be treated differently. I was unaware of and_yield when I picked up this ticket, but +1 to @JonRowe's point IMO the contracts and use cases for with(a_block) and_yield and with(a_block, ..., a_block) should be defined before proceeding further to avoid ambiguous apis. I will defer to your judgement, and am happy to attempt an implementation once the decision is made.

kaiwren avatar Sep 18 '18 11:09 kaiwren

They're not different enough, and can be passed around... Consider:

2.5.1 :001 > def a(&block)
2.5.1 :002?>   puts block.inspect
2.5.1 :003?>   block
2.5.1 :004?> end
 => :a
2.5.1 :005 > a =  a { |a| }
#<Proc:0x00007fd290a01440@(irb):4>
 => nil
2.5.1 :006 > b = a(&proc { |b| })
#<Proc:0x00007fd2909e1c58@(irb):5>
 => nil
2.5.1 :007 > c =  a(&lambda { |c| })
#<Proc:0x00007fd2909d0548@(irb):6 (lambda)>
 => nil
2.5.1 :008 > Proc === a
 => true
2.5.1 :009 > Proc === b
 => true
2.5.1 :010 > Proc === c
 => true

JonRowe avatar Sep 18 '18 16:09 JonRowe

@JonRowe @benoittgt @ioquatix Working toward treating args and blocks in a more similar manner, I've done some work to try to tease out ReceivedMessage and ReceivedMessages as concrete classes from Proxy and delegate to them as a first step. Do let me know if this makes sense, or if I should move in a different direction or just drop it entirely. https://github.com/rspec/rspec-mocks/compare/master...kaiwren:refactor-extract-ReceivedMessage-from-Proxy

kaiwren avatar Sep 23 '18 17:09 kaiwren

I'm not sure why this was never merged, migrated to the monorepo as rspec/rspec#114

JonRowe avatar Nov 30 '24 10:11 JonRowe