I certainly appreciate the thoughtful and lengthy reply, but I think it misses the main part of my problem -- perhaps my original explanation of my issue was lacking in clarity. This isn't a question of refactoring; I can easily refactor the method as you describe but this doesn't resolve the issue (indeed, it just leads to an increased number of public methods in the model with no real benefit). So, to more clearly illustrate my question, forget about my original method and instead let's say I have three methods as follows:
def self.find_something(inputs) .. do stuff here end def self.add_something(inputs) .. do stuff here end def self.find_or_add_something(inputs) x = self.find_something(inputs) if !x self.add_something(inputs) x = self.find_something(inputs) end x end I can easily spec the find_something and add_something methods so I know that they work correctly. My question is, how do I write a spec for find_or_add_something in a way where I don't have to re-test the behavior of the individual find_something and add_something methods a second time, and can just test the overall logic of that method? It's helpful to know that Mocha is available to assist with this, but I'd rather not switch to an entirely different framework for mocks & stubs just because of this one method... On Mon, Jul 20, 2009 at 11:45 PM, Stephen Eley <sfe...@gmail.com> wrote: > On Mon, Jul 20, 2009 at 8:33 PM, Barun Singh<baru...@gmail.com> wrote: > > > > I want to find a way to write a spec for this method that does both of > these > > things: > > (1) stub out the do_something method (that method has its own specs) > > (2) not stub out the logic in the else statement, (there is some complex > > logic in there involving sql queries on multiple tables and i explicitly > > want to make it touch the database so that I can examine the state of the > > database after the method is run in my spec. a lot of complex stubs > would > > be required to do this while also having the spec that is actually > useful) > > Okay, first off -- I think part of your problem is that your focus is > too short. It sounds like you're asking how to write different specs > for various lines of the _internals_ of this method. That's too > fine-grained. Spec the method from the _outside:_ write specs that > say "If I give the method these inputs, this should happen." Then > test that the end result of the method's execution was what you > expect. > > I'm betting that probably sounds really difficult, given the "complex > logic" you're describing. And that would be my second, more important > tip: if your method is so complicated that you can't spec it from the > outside, you should take that as a sign. Spaghetti code is usually > very hard to spec. > > Reconsider your problem. Try breaking it down into smaller, more > easily specified pieces, and then recompose them into the logic that > you need. I don't know your business rules; it looks to me like the > basic task here is "Keep twiddling with the database until I get what > I'm looking for, as many times as that takes." I don't know why > that's necessary, but if it is, recursion doesn't seem like the most > elegant answer to me. Recursive calls are usually made in small > functions that don't have side effects. This is the opposite. > > What would I do instead? Again, without knowing your specific > business, I'd probably break it into pieces, and then turn the > requirement inside out and do it with a loop: > > def self.find_something(inputs) > <find something in db based on inputs> > end > > def self.add_something(inputs) > <add something to db based on inputs> > end > > And then, wherever you *would* have called mymethod(inputs), you can > instead write this, which will loop zero or more times: > > add_something_to_db(inputs) until x = find_something(inputs) > x.do_something > > ...Whether or not you still want to wrap that in a method probably > depends on whether you need to call it more than once. But I'll bet > you that the "add_something" and "find_something" methods are both > easier to spec from the outside. If they're still too complex, then > break them down too. > > > Oh, and final trivia note: > > > any thoughts on how to accomplish this? two approaches that would make > > sense but don't seem to be possible as far as i know are: > > (A) stub out the do_something method for any instance of the class (i > can't > > stub it out for a particular record because the records don't exist at > the > > time of calling the method and I'm not stubbing the creation process) > > (B) stub out "mymethod" only for the second time it is called (obviously > i > > can't stub the method out entirely since that's the method i'm testing) > > You can do both of these with the Mocha mocking library, if you really > want to. It puts an "any_instance" method on Class that you can use > to mock or stub; and you can specify ordered chains of results to > return. > > But again, you shouldn't need to if your methods are atomic enough. > If 'do_something' properly belongs outside the scope of what you're > testing, then maybe that's a sign that you shouldn't put it in the > same block of code. And if you find a need to declare that your > method should behave entirely differently on a second invocation, > maybe it shouldn't be one method. > > Simplify relentlessly. Once I figured out how to make my code > simpler, I found that I wasn't mocking nearly as much as I used to. > > > -- > Have Fun, > Steve Eley (sfe...@gmail.com) > ESCAPE POD - The Science Fiction Podcast Magazine > http://www.escapepod.org > _______________________________________________ > rspec-users mailing list > rspec-users@rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
_______________________________________________ rspec-users mailing list rspec-users@rubyforge.org http://rubyforge.org/mailman/listinfo/rspec-users