On Fri, Sep 5, 2008 at 2:15 PM, Christopher B.
[email protected] 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
- mock that class directly
- use dependency injection along with rspec-built mocks
- 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]