[rspec-users] Mocking a 3rd party call, but with a few exceptions

Christopher Bailey chris at cobaltedge.com
Tue Sep 16 14:57:59 EDT 2008

I've played around a bit on this.  The constant setting with the service
locator pattern looked good, but I couldn't get the undoing/resetting of the
constant to work properly, not sure what I was doing wrong there.
This appears to be a solution though:
In my spec_helper.rb (which is required by all specs), I put this:

  config.before(:each) do
      # Setup fake geocoding unless told not to
      unless @do_not_mock_geocoding
        fake_geocode = OpenStruct.new(:lat => 123.456, :lng => 123.456,
:success => true)


Then, in tests where I want real geocoding, I just
set @do_not_mock_geocoding to true.

On Fri, Sep 5, 2008 at 8:36 PM, Pat Maddox <pergesu at gmail.com> wrote:

> On Fri, Sep 5, 2008 at 2:15 PM, Christopher Bailey <chris at cobaltedge.com>
> wrote:
> > I use geocoding in our app, and it permeates most of the core
> functionality.
> >  Because it makes a call out to Google or Yahoo or what not to do the
> > geocoding, I'd like to mock this for the bulk of my tests, except for the
> > few tests that actually do stuff where they need the real data.  I had
> > started wrapping all my specs with the equivalent (but a DRY form) of:
> > GeoInfo = Struct.new(:lat, :lng, :success)
> > describe "with fake geocoding" do
> >   before(:all) do
> >     fake_geocode = GeoInfo.new(123.456, 789.012, true)
> >
> >
>  GeoKit::Geocoders::MultiGeocoder.stub!(:geocode).and_return(fake_geocode)
> >   end
> >   # bulk of tests are here
> > end
> > describe ... #other tests that want real geocoding here
> > But, that just seems like a poor way to do it.  I'm wondering, how can I
> > make GeoKit::Geocoders::MultiGeocoder fake by default, and then in the
> few
> > cases where I want it to be real, "un-stub" it or whatever you'd call it?
> I would probably write a thin wrapper around that class, exposing the
> functionality you need with the interface you want.  Then in your
> tests, you can
> 1) mock that class directly
> 2) use dependency injection along with rspec-built mocks
> 3) use dependency injection with hand-built fakes
> The difference between 2 and 3 is that with rspec-built mocks, you're
> going to do stuff like
> @mock_geocoder = mock("geocoder", :geocode => fake_geocode)
> and with a hand-built fakes, you'd do something like
> class FakeGeocoder
>  def initialize
>    @geo_info_class = Struct.new(:lat, :lng, :success)
>  end
>  def geocode
>    @geo_info_class.new(123.456, 789.012, true)
>  end
> end
> You mentioned that this geocoding is a core feature of your app, so
> going with a hand-rolled fake may give you more flexibility to do some
> more sophisticated stuff.  It has the added benefit of forcing you to
> really think about the abstraction in your domain since you're going
> to be implementing it twice (once as a wrapper around that class, and
> once for testing purposes).
> So there are the standard ways of doing DI [1].  In cases like this, a
> favorite trick of mine is to have an aliased constant that points to
> the implementation you want.  For example, in development.rb you might
> have
> Geocoder = GoogleGeocoder # GoogleGeocoder is your production wrapper
> and in test.rb you have
> Geocoder = FakeGeocoder
> This is kind of a clever spin (if I do say so myself :) on the Service
> Locator [2] pattern.  Basically, instead of having one central object
> that knows how to map domain abstractions to implementations, you just
> define a constant to represent the domain abstraction, and then point
> it to the real implementation you want.  So now your production code
> just references Geocoder all over the place, you write unit tests for
> GoogleGeocoder to make sure it works, and you get your FakeGeocoder
> throughout your other unit tests for free.
> If you need to change implementations, you can just reassign the
> constant and ignore the warnings...but if you plan to have multiple
> implementations that you use throughout the app, you'll probably want
> to go for more traditional dependency injection.
> Pat
> [1] Jim Weirich gave a talk at OSCON 2005, the slides for which I
> can't find anymore (!!).  It basically showed traditional DI and some
> neat stuff you can do with Ruby to make it much simpler.
> [2]
> http://www.martinfowler.com/articles/injection.html#UsingAServiceLocator
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users

Christopher Bailey
Cobalt Edge LLC
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20080916/1a479c31/attachment-0001.html>

More information about the rspec-users mailing list