# Mocking a 3rd party call, but with a few exceptions

**URL:** <https://www.ruby-forum.com/t/mocking-a-3rd-party-call-but-with-a-few-exceptions/147844>\
**Category:** RSpec\
**Created:** [September 5, 2008, 11:17pm UTC](https://www.ruby-forum.com/t/mocking-a-3rd-party-call-but-with-a-few-exceptions/147844 "2008-09-05T23:17:15Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![fxp](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/a5b964/32.png) [@fxp](https://www.ruby-forum.com/u/fxp)\
**Post date:** [September 5, 2008, 11:17pm UTC](https://www.ruby-forum.com/t/mocking-a-3rd-party-call-but-with-a-few-exceptions/147844/1 "2008-09-05T23:17:15Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![fxp](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/a5b964/32.png) [@fxp](https://www.ruby-forum.com/u/fxp)\
**Post date:** [September 6, 2008, 5:39am UTC](https://www.ruby-forum.com/t/mocking-a-3rd-party-call-but-with-a-few-exceptions/147844/2 "2008-09-06T05:39:10Z")

</div>

On Fri, Sep 5, 2008 at 2:15 PM, Christopher B.  
[removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> 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 W. 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]

> **[Inversion of Control Containers and the Dependency Injection
	pattern](https://www.martinfowler.com/articles/injection.html)**
>
> Explaining the Dependency Injection pattern, by contrasting it with Service Locator. The choice between them is less important than the principle of separating configuration from use.

---

<div class="post-metadata">

**Author:** ![fxp](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/a5b964/32.png) [@fxp](https://www.ruby-forum.com/u/fxp)\
**Post date:** [September 16, 2008, 11:05pm UTC](https://www.ruby-forum.com/t/mocking-a-3rd-party-call-but-with-a-few-exceptions/147844/3 "2008-09-16T23:05:56Z")

</div>

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)

GeoKit::Geocoders::MultiGeocoder.stub!(:geocode).and\_return(fake\_geocode)  
end  
end

Then, in tests where I want real geocoding, I just  
set @do\_not\_mock\_geocoding to true.
