-
-
Notifications
You must be signed in to change notification settings - Fork 359
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add and_invoke
for sequential mixed (return/raise) responses.
#1411
Conversation
Ah, did not realize |
How do you feel about accumulating configured responses? allow(foo)
.to receive(:bar)
.and_return(1)
.and_return(2) won't work. In the same way, it could be possible to use It's very similar to what you suggest with Side note: it's kind of confusing that it is possible to configure several responses at the moment, but only one, the last defined, actually comes into play. |
I'm surprised this works, too, @pirj. I'm a bit worried about changing it because in all liklihood there are people who have done something like: def default_fixture
instance_double(Foo).tap |dbl|
expect(dbl).to receive(:color).and_return(:blue)
end
end
it "bar" do
expect(baz(default_fixture)).to eq(4)
end
it "bars slightly differently under certain circumstances" do
fixture = default_fixture.and_return(:aqua)
expect(baz(fixture)).to eq(5)
end However, your example raises a neat idea that would require a bit more careful architecture change: allow(foo)
.to receive(:bar)
.and_return(1) # <== sets terminal action to AndReturnImplementation.new([1])
.and_then_return(2) # <== replaces terminal action with ChainedTerminalAction.new([ existing_action, AndReturnImplementation([2]) ])
.and_then_raise(RuntimeError, "failure!") # <== calls ChainedTerminalAction.append(AndRaiseImplementation.new(...)) The trick here would be the earlier chained implementations need to be extends to support not repeating the last element in their list, or track if they have been 'consumed' or not so that the chained action can move onto the next item. Likewise, if we want to keep existing behavior, we'd have to repeat the last action of the last chain element. Alternatively we could say that once you opt into this chaining construct, you must build an explicit chain of response elements. Overall, I quite like this approach, but I'll hold off on making the change until I hear from more maintainers about the direction. Note that my original |
I believe that chaining configured response qualifiers would imply those responses being called in order. It needs a second thought on how countable qualifiers should work though. receive(:foo)
.and_return(1).twice
.and_return(2).once # return 1, 1, then 2 vs receive(:foo)
.twice
.and_return(1)
.and_return(2) # ok receive(:foo)
.thrice
.and_return(1)
.and_return(2) # the number of configured responses doesn't add up. Should we return `nil` on the third call? receive(:foo)
.twice
.and_return(1)
.and_return(2)
.and_return(3) # Error! The number of configured responses exceeds the expected number of calls Semi-related rspec/rspec-expectations#1252 |
I don't know, are you saying that in the case where we've qualified the counts with expectations that the responses should switch from being overridden to appending in a chained fashion? The inconsistency with the behavior when you call Even in qualified cases where expectation counts have been specified, people may still be relying on the override behavior (like my example from above). It seems backwards compatibility is a reasonable top priority for this gem. |
Initially I was going to suggest that as |
Co-authored-by: Jon Rowe <mail@jonrowe.co.uk>
Thanks for this proposal. I love the idea. Last version of code looks good to me. Thanks for the clarity of the PR. |
@benoittgt Great, glad to hear it. I'm good with where this is at whenever we have quorum to merge. Thanks! |
Thanks! |
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.
Oh, I'm late for the party 🐌
It looks amazing, thanks for the contribution!
allow(dbl).to receive(:foo).and_invoke(lambda { raise "failure" }, lambda { true }) | ||
|
||
expect { dbl.foo }.to raise_error("failure") | ||
expect(dbl.foo).to eq(true) |
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 feature description says "if the message is received additional times", maybe add an additional line repeating this one to prove/indicate that?
Add `and_invoke` for sequential mixed (return/raise) responses.
…voke Add `and_invoke` for sequential mixed (return/raise) responses. --- This commit was imported from rspec/rspec-mocks@70fbe12.
This commit was imported from rspec/rspec-mocks@7013c5f.
…voke Add `and_invoke` for sequential mixed (return/raise) responses. --- This commit was imported from rspec/rspec-mocks@77ed590.
This commit was imported from rspec/rspec-mocks@8635dda.
In tests I occasionally encounter things like this:
It's commonly suggested to do similar things on StackOverflow, for example. It seemed a common enough problem to try to generalize a solution that is a bit more readable. This patch allows the following alternative:
and_invoke
behaves likeand_return
with regards to sequential calls, matching expected call counts, and repeating the last call indefinitely in cases whereallow
is used vs.expect
withexactly(n).times
.I alternatively considered some more magical approaches like:
Ultimately, I thought Procs were more "native", if slightly ugly.