---
title: Have You Ever... Faked It?
teaser:
tags: web,testing,ruby
author: Joe Ferris
published_on: 2009-10-21
---

![fake it](http://media.tumblr.com/tumblr_krvk95kMJb1qzocnw.jpg)

I'll admit it - I've faked it. Sometimes, you just can't wait for a service to
finish and you just want to fake a satisfactory response. There are lots of
techniques for doing this: [stubs](http://xunitpatterns.com/Test%20Stub.html),
[mocks](http://xunitpatterns.com/Mock%20Object.html),
[spies](http://xunitpatterns.com/Test%20Spy.html), and
[fakes](http://xunitpatterns.com/Fake%20Object.html). A full fake object will
require more up-front effort than a quick stub, but they can be more reusable,
reliable, and fail-proof.

## Keeping your tests local

Probably the first reason every developer encounters that drives them to [test
doubles](http://xunitpatterns.com/Test%20Double.html) of any kind is an
external service. Writing tests that interact with a live server is bad for a
number of reasons: it's slow, it's hard to setup your test, and your tests will
likely
[interact](http://xunitpatterns.com/Erratic%20Test.html#Interacting%20Tests)
with each other (or other developers' tests). You'll also eventually run into
that frustrating situation where server downtime results in a development
blackout.

## Overriding methods

Ruby is extremely flexible with its class definitions, to the point that you're
allowed to reopen and append (or redefine) methods at any point. I've seen lots
of projects where this is used to simply white out pieces of the application
that developers don't want running in tests:

```ruby
# The production code
# app/models/event.rb

class Event < ActiveRecord::Base
  before_validation :geocode
  # ...
  private
  def geocode
    geo = GeoKit::Geocoders::MultiGeocoder.geocode(address)
    if geo.success
      self.lat, self.lng = geo.lat, geo.lng
    else
      self.errors.add(:address, 'Unable to identify your location.')
      false
    end
  end
end

# The override
# test/support/geocoding.rb

module GeoKit
  module Geocoders
    class MultiGeocoder < Geocoder
      def self.geocode(location)
        loc = GeoLoc.new
        loc.lat = 1
        loc.lng = 1
        loc.success = true
        loc
      end
    end
  end
end
```

That little snippet lets you hit the ground running. Your tests don't need to
hit an external server, and you can test that a latitude and longitude is
assigned when the record is created. However, you can't test the negative
case, and if you want to write tests for geo-spatial search, you're out of
luck.

## Using stubs

If you need different geocoded results in different tests, a straight-up
override won't do it for you. At this point, a developer might turn to stubs
and mocks:

    describe Event do
      it "should geocode a valid location" do
        loc = GeoLoc.new
        loc.lat = 100
        loc.lng = 200
        loc.success = true
        GeoKit::Geocoders::MultiGeocoder.
          stubs(:geocode).
          with('123 Happy Street').
          returns(loc)
        event = Factory.build(:event, :address => '123 Happy Street')
        event.save
        event.lat.should == loc.lat
        event.lng.should == loc.lng
      end

      it "should add an error message with an invalid location" do
        loc = GeoLoc.new
        loc.success = false
        GeoKit::Geocoders::MultiGeocoder.
          stubs(:geocode).
          with('123 Sad Lane').
          returns(loc)
        event = Factory.build(:event, :address => '123 Sad Lane')
        event.save
        event.should_not be_valid
      end
    end

You'll probably need to stub out geo locations several times throughout the
test suite, so wrapping these up in helper methods helps:

    describe Event do
      it "should geocode a valid location" do
        loc = build_geoloc(:lat => 100, :lng => 200, :success => true)
        stub_geoloc!('123 Happy Street' => loc)
        event = Factory.build(:event, :address => '123 Happy Street')
        event.save
        event.lat.should == loc.lat
        event.lng.should == loc.lng
      end

      it "should add an error message with an invalid location" do
        loc = build_geoloc(:success => false)
        stub_geoloc!('123 Sad Lane' => loc)
        event = Factory.build(:event, :address => '123 Sad Lane')
        event.save
        event.should_not be_valid
      end
    end

However, this leads to a few new problems:

* Most of the time you create an Event in a test, you don't care about
  geocoding, so you'll probably still need to add an override.
* Writing stubs like this in Cucumber tests is tricky - you need to make sure
  your stubs are torn down, and refactoring your implementation will noisily
  cause scenarios to fail. This isn't what you want from an integration test.
* You can't test the stubs themselves, and these methods are difficult to
  reuse, especially between projects.

## Swappable components

One of the best pieces of programming advice I've ever received is this:
"separate the pieces that change from the pieces that don't." Logic that
doesn't directly apply to your domain, such as geocoding and credit card
processing, are likely to change. Which provider will you use? Do you need
failover support? Adding the code (and tests) to your models is likely to
increase churn and noise in the essential models of your application.
Extracting these other concerns to external components helps reduce this noise.
Writing these components so that the implementation can be swapped out will
take you even further.

Luckily, in the case of GeoKit, this has all been done for you. GeoKit supports
several Geocoders, and the list of Geocoders that will be used can be
configured per-environment. The list of Geocoders is configured via the global
"provider_order" setting:

    GeoKit::Geocoders::provider_order = [:fake]

GeoKit will look for a camelized constant nested under `GeoKit::Geocoders` for
each provider specified, so you can write a fake geocoder like this:

```ruby
module GeoKit
  module Geocoders
    class FakeGeocoder < Geocoder
      def self.geocode(location)
        # return a GeoLoc instance
      end
    end
  end
end
```

If you maintain a global registry of locations mapped to latitude and longitude,
you can write steps like the following:

    Given /^the following geolocations:$/ do |table|
      table.hashes.each do |hash|
        location = hash['Location']
        lat = hash['Lat'].to_f
        lng = hash['Lng'].to_f
        GeoKit::Geocoders::FakeGeocoder.locations[location] = [lat, lng]
      end
    end

Because GeoKit allows you to swap out the geocoder implementation without
changing your model code, there's no test-specific code in your model, no
overrides to look for, and no extra churn in your model if you decide to switch
providers.

## Keeping it fresh

One potential issue with the above faking strategy is that there are a number
of globals involved: the GeoKit provider is specified globally, as is the
registry of fake locations. This means that you'll need a teardown phase to
clear the registry. In this case, there's little reason to swap out
implementation at runtime or between tests. However, if you're faking out
something that changes live, these global won't do.

When possible, I recommend accepting the external component as a parameter. If
Event could take a GeoKit provider as a parameter, you could write tests like
the following:

    describe Event do
      it "should geocode a valid location" do
        fake_geocoder = FakeGeocoder.new('123 Happy Street' => [100, 200])
        event = Factory.build(:event, :address => '123 Happy Street',
                                      :geocoder => fake_geocoder)
        event.save
        event.lat.should == 100
        event.lng.should == 200
      end
    end

There's no teardown required. The `FakeGeocoder` is discarded after the test
executes, so the mapped locations don't live beyond the test.

## Keeping it real

When faking out a component, make sure you adequately reproduce the actual
expected behavior. When faking out payment gateways, you'll need to cover a
variety of error responses and so on. When faking out a geocoder backend, you
may want to simulate timeout errors and other failures. Make sure you have
enough test cases that your application will catch the edge cases that occur
when using the real thing.

## Apply liberally

Developers are forced to extract code into external components when it's just
not feasible to use the same code in the tests, as is the case when contacting
external services. However, there are many pieces of behavior that can be
extracted that don't need to be: searching, authentication, and so on.

Although developers usually only use stubs and mocks when they need to, many
developers eventually prefer the isolation, speed, and declarative nature of
such doubles. If you start experimenting with fakes in place of mocks for
external services, you may find that there are pieces of your code that you
could swap out just to keep the churn down in your models, or to keep your
tests focused on what they're testing.

How about you? Have you ever...faked it?

## What's next

If you found this useful, you might also enjoy:

* [Geocoding on Rails][geocoding]
* [Test-Driven Development with RSpec and Capybara][tdd]

[geocoding]: http://geocodingonrails.com
[tdd]: https://thoughtbot.com/upcase/test-driven-rails
