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
File.stub(:exist?).with(x) causes NoMethodError when File.exist?(y) is called #20
Comments
My instinct is that this would be a big enough change in current behaviour as to be not only disruptive, but potentially harmful to anyone who's specs are stubbing a method that does something destructive. Agreed that the error message is misleading and can be improved. Agreed that we should have a way to delegate to the real method, but I'd want it to be explicit. WDYT? |
I hadn't considered the case where you're stubbing something destructive. Now that you bring it up, I agree that I like the idea of having an explicit way to allow delegation to the real method when other arguments are passed, like you suggest. Would it just be an additional method call on the fluent interface? Something like this, perhaps:
...but And I also agree that a better error message than So yes, I agree with your conclusions :). I'm glad I didn't go ahead and start working on this... |
Actually, thinking about this some more, I think that you should not be limiting the stub arguments by calling Obviously, others may not expect it to work this way, especially given how rspec-mocks work currently. I'd prefer the API work how I've suggested, but given the past behavior and the potential for breakage, I'm fine with doing what you've suggested. Still, it's nice not to be stuck with a sub-optimal API due to expectations of how things worked previously, and if you're going to break an API, a major release (i.e. RSpec 2) is the time to do it. |
Tell you what - why don't you write to the rspec-users list and run this by a wider audience. Ask people to comment here rather than on the list so we can have the discussion all in one place. |
I can't remember the details, but I recall stumbling upon this situation in some form. I have a couple of projects each with over 1500 specs, so it will take me a bit to figure out where I encountered this before I could contribute much to the conversation. But I will say this: if I'm stubbing, I never want the real thing to be called without me making an explicit step to do so. If I have something stubbed as .with(x) to have the real method called if y is passed would violate the principle of least surprise. I'd be quite surprised! I doubt that it's very good idea to begin with, but I've wondered if it would be possible for stubbing to keep track of all the different permutations that we might want to define, such as: my_object.stub(my_method) Could all of those be mapped somehow? I can't remember what it was, but there was a time when I wished I could do that. It might have been a terrible spec on my part, though. I can't recall. |
@phillipkoebbe - that actually works now as/is. |
Funny you mention that--I was surprised that it didn't delegate to the original method definition when y was passed. |
My bad. I didn't realize we were talking about v2. I haven't made the move to Rails 3 / RSpec 2 yet, so my mind is still thinking in v1. Is the behavior I described in v1 or am I just imagining things? Heh heh. I guess we aren't all surprised by the same things. I suppose what I was expecting was if something is stubbed, it's stubbed, regardless of what is passed to it. But now that I think about it more, considering your comment and my own previous thought about the various permutations, I can see how your expectation actually makes more sense. The stubbing framework can do only what I tell it to do, so if I don't tell it what to do with y, then it probably shouldn't do anything. In my reconsidered opinion, then, my preference would be to delegate to the original only if my_object.stub(my_method) is not defined. Since this is a blanket stub irrespective of arguments, it should be considered a "catch all" for stubbing. If that is not defined, then any argument passed that is not explicitly stubbed should be delegated to the original. That, at the moment, makes sense to me. |
What if you tell the stub something like:
Would that be clear enough to assume delegation to the original with other arguments than those stated by exclusively_with / explicitly_with ? Just that it is a bit less clear when stubbing this way in two separate statements (assuming exclusively_with for example):
Seems that the 'exclusivity' of the second statement prevails, which (I think) would not be the desired behavior. Any thoughts on this? |
@txus--interesting idea. I'm not sure the name is right, though--I had to read it several times before I figured out what it would do. Overall, I'd still prefer we use David: you brought up the issue of existing specs that stub a dangerous method, and that my suggested change could now allow the dangerous method to be called. Given the current behavior, I doubt there are many (if any) cases of this in the wild. Consider this code:
Your concern, as I understand it, is that the 2nd spec would now do the dangerous thing. And this is correct. But the 2nd spec won't pass with rspec-mock's current behavior, as a |
@myronmarston - that may be so, but it doesn't protect against refactorings that change the implementation such that it exposes the call to the dangerous method. What about something like this:
|
+1 for this one, call_original is definitely explicit. |
I like this syntax quite a bit, too. That said, this is definitely more verbose than |
Raise with unexpected args message instead of NoMethodError when a stub I'm going to close this one and open a new one to add call_original |
I'm specing some behavior that depends on the existence or absence of a file. I'm doing so with a stub:
This works fine except that described_class's initializer also calls File.exist? with a different argument. That causes an error:
NoMethodError: undefined method 'exist?' for IO:Class
.It seems to me that an argument-limited stub of an existing method should delegate to the original method definition when the method is called with a different argument, rather than raising a NoMethodError. My stubbing of File.exist? for file x should not effect File.exist?(y).
The text was updated successfully, but these errors were encountered: