-
-
Notifications
You must be signed in to change notification settings - Fork 358
Add allow(...).to receive_message_chain #467
Conversation
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an awful small class to get its own file. Can we find another place for it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
framework.rb?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not leave it in receive.rb
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm in favor of leaving it in receive.rb
.
I don't see anything here for: expect(k).to receive_message_chain(...)
allow_any_instance_of(k).to receive_message_chain(...)
expect_any_instance_of(k).to receive_message_chain(...) |
@recorded_customizations = [] | ||
end | ||
|
||
[:and_return, :and_throw, :and_raise, :and_yield].each do |msg| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're missing and_call_original
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stub chain does not work with and_call_original:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you found a bug in stub_chain
that we should fix. In your example, a
is not test double. Also we should fix the wording so it says "pure test double" vs "partial double" rather than using mock
in the message.
Also, what about block implementations? I don't see any specs showing that working. |
I've now added these. |
@@ -2,7 +2,7 @@ | |||
require 'delegate' | |||
|
|||
describe "and_call_original" do | |||
context "on a partial mock object" do | |||
context "on a partial double" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find "partial double" confusing here, how about "on a method double" instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you find confusing about the term "partial double"? "MethodDouble" is an internal class within rspec-mocks, but it's not a concept we expose publicly and this context isn't really about that...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A test double to me is an entirely fake object. A method double indicated we're faking only one method. I thought method double was a more widely known concept than just rspec?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I've never heard it used outside of RSpec, but I don't have much exposure to using test doubles outside of an RSpec context.
Anyhow, in #444 the term we decided on for the new config option was "partial double", so we should be consistent here. If we want to change how we refer to the concept, we can, but that is a bigger issue that we should address in a separate PR if we do address it. For now, being consistent with that naming is best, I think.
Good work @samphippen! Will need a change log entry of course and I'd like to see these commits squashed? |
Matchers::ReceiveMessages, | ||
Matchers::ReceiveMessageChain, | ||
] | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be moved into a constant? As it is implemented now, every time it is called in allocates a new array object, which seems wasteful, given that conceptually, it's a constant.
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some other things to consider testing here:
expect().to receive_message_chain
-- this is a feature that the old syntax lacked, but I don't see a reason not to support it. It's one thing I like about the new expect/allow + matcher system: we easily get feature parity for both message expectations and message allowances. That said, if it's a bunch of work to get this to work, I'm not opposed to disallowing it....but I'd want a test here that documents that fact. As it stands now, it's completely undocumented what happens when you useexpect
withreceive_message_chain
.receive_message_chain("foo.bar.bazz")
-- this is one of the thingsstub_chain
supports and we may as well support it. I think it should "just work" but a test to confirm and document would be great.receive_message_chain(:foo, :bar, :bazz => "return value")
-- this works forstub_chain
(seestub_chain_spec.rb
) and should either work here or be documented as not working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any instance expect proved to be difficult, everything else exists now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! One other thing I forgot to mention: the negative case (e.g. allow().not to receive_message_chain
). I think you already have code to prevent this but specs to document the behavior would be good. Given you already have the code in place for it, it'd be good to "break" it somehow to confirm the tests you add for this can properly fail with a useful failure message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add a spec for this later :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@myronmarston I have to get on a plane now, but I think I've addressed all the feedback. Can you take another look through this and tell me what you think when it goes green? Thanks 😄 I'll add a changelog in ~8 hours when I get off the plane. |
I'm down with this. I particularly like how chain_on got pushed into one place. |
@myronmarston I've updated based on your feedback. I've noticed that if you've set an expectation/allowance before you created a chain, and that expectation/allowance returns nil you get a stubbing method on nil warning. I'm not sure how you feel about that, but I'm pretty sure it's an inevitable consequence. |
@myronmarston can you take a look at this please? |
chain_on(matching_stub.invoke(nil), *chain, &@block) | ||
elsif matching_expectation = find_matching_expectation | ||
chain.shift | ||
matching_expectation.actual_received_count -= 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unclear to me why this line is here...can you explain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expectations in the tests were failing due to the expect() setting up an expectation that exactly one call is made. I realised this is because we invoke the first expectation once, and then when the entire chain is called, that first expectation gets called a second time. I guess to me, the least surprising behaviour is that the tests as they are pass. I guess I could increment the expected received count instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get it now. Instead, what do you think about adding a invoke_without_changing_received_count
method to MessageExpectation
? to me, it feels hacky to manipulate the received counts directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@myronmarston that's a really good call. I love how you always come up with this stuff.
It's the correct behavior. That said, you could avoid it in your test by doing something like: allow(obj).to receive(:foo).and_return(double)
allow(obj).to receive_message_chain(:foo, :bar, :bazz) ...because then it would be stubbing the returned This is looking great, @samphippen :). |
Woohoo this is coming along nicely! Good job :) 👍 |
@myronmarston thoughts on |
LGTM. Let's squash this down and merge it! BTW, if it's not too much trouble to backport this to 2.99, that would be nice, as I suspect that transpec will be gain the ability to do the conversion and it would be nice to convert to this as folks upgrade. Not essential though. |
@myronmarston /me wipes sweat of brow. Well that was fun. I'll squash it down to one commit, and then cherry pick it across to 2-99 assuming that's sufficiently easy. |
Add allow(...).to receive_message_chain
woohooo!! This makes me a really happy camper 👍 |
@myronmarston I had a crack at reconciling this with 2-99 and failed to merge it properly. I'll take another go tomorrow, but this may be unsurprising: these things are now quite out of sync. |
@myronmarston couldn't merge this into 2-99. I think we'll just leave it for 3.0.0? |
What happened to: expect(something).to receive_message_chain('whatever.something').with(?) ? |
I'm fine with that. @mhenrixon -- did |
Oh sorry about that I thought you guys could read my mind! No stub_chain didn't support this but stub_chain wasn't an expectation either so to make it truly useful I would |
The reason we now support expect(obj).to receive_message_chain("foo.bar.bazz").with(1).with(2).with(3)
obj.foo(1).bar(2).bazz(3) ...or like this: expect(obj).to receive_message_chain("foo.bar.bazz").with(1)
obj.foo(1).bar(1).bazz(1) ...or like this: expect(obj).to receive_message_chain("foo.bar.bazz").with(1)
obj.foo(1).bar.bazz ...or like this: expect(obj).to receive_message_chain("foo.bar.bazz").with(1)
obj.foo.bar.bazz(1) It's ambiguous (or at least, reasonable to interpret in different ways). If you care what arguments are received, then use the existing tools available to you. |
Could you guide me to how I could achieve that myself now that all the terribly tough work has been done by you guys already? Could I hack that together myself somehow? |
allow(obj).to receive_message_chain(:foo, :bar).and_return(double)
expect(obj.foo.bar).to receive(:bazz).with(/adfads/) |
@myronmarston does |
Yes. In fact, it's only been in one release: 3.0 (which is also the most recent release). We have no plans to ever remove it. |
@myronmarston the Example of usecase: Say I want to test a single-fire object like Also, there are probably some piping libraries that could benefit from this as well, think: |
@stephan-nordnes-eriksen -- please open a new issue. Feature requests posted at the bottom of a merged pull request tend to get lost in the shuffle. |
Yea, I agree, but I don't want to create a feature request if it is something that won't be possible to implement. |
First pass. Comments on the back of a PR :)