Testing with Mox

Sometimes I avoid learning new things because I’m lazy, pressed for time, or for some other reason couldn’t be bothered to figure them out.  I write a lot of tests these days, but I’ve been putting off figuring out how to use Mox because it was usually just as fast for me to roll my own solution and because the documentation seemed like it was written for folks who know what they are doing already and are just looking for the answer to the how question.  Let me explain Mox as I understand it and give some examples how to use it for testing applications that use web services or make remote http calls.

Let’s say you’ve got this Python Class that looks something like this:

"""Find locations."""

__author__ = 'bmichalski@gmail.com (Brian Michalski)'

import json
import urllib

class LocationFinder(object):
  """Find the geographic location of addresses."""

  def __init__(self, urlfetch):
    """Initialize a LocationFinder.

    Args:
      urlfetch: Backend to use when fetching a URL.
        Should return a file-like object in response to the urlopen method.
    """
    self.urlfetcher = urlfetch

  def find(self, address=''):
    """Find the latitude and longitude of an address.

    Args:
      address:  String describing the location to lookup.

    Returns:
      Tuple with (latitude, longitude).
    """
    base_url = 'https://maps.google.com/maps/api/geocode/json'
    params = urllib.urlencode({
      'sensor': 'false',
      'address': address
    })
    url = '%s?%s' % (base_url, params)

    result = self.urlfetcher.urlopen(url)
    data = json.loads(result.read())
    location = data['results'][0]['geometry']['location']
    return (location['lat'], location['lng'])

The code is pretty simple, you can run it using something as simple as:

finder = LocationFinder(urllib)
print finder.find('1600 Amphitheatre Parkway, Mountain View, CA')

What’s important is that LocationFinder takes urllib as an argument. This is a kind of poor example because urllib isn’t another class that really needs mocking, but if you were developing on AppEngine or other environments where outbound connections weren’t as straight forward you could pass in the instance of your outbound connection library.

For demonstrative purposes, let’s pretend one of a few things is happening. 1. We can’t get an outbound internet connection to actually test against Google. 2. Google is too slow to test against. or 3. The service we’re testing against requires a complicated authentication handshake beforehand. None of these three cases are actually at play here on my laptop, but you could imagine wanting to isolate your testing from Google in the event that service goes down or is temporarily unavailable to you.

Mox to the rescue. Using Mox, we can make a fake urllib which, by default, doesn’t know anything about the existing urllib. Since we only call the urlopen function and don’t care about any other externals, all we have to do is define that method on our fake urllib and tell it what to return when it’s called. I find the syntax a bit strange, but to define the method you just call it and pass it the expected values (or matchers to broadly match your expected values) and then add .AndReturn(return value here) to wire up it’s return. When urllib.urlopen is called with the parameters you’ve specified it will return the return value you’ve stored otherwise you’ll get an error saying that the expected parameters don’t match what it’s actually being called with or the expected return doesn’t match the actual return (putting the the return from a void into a variable for example). Speaking of examples, here’s how I could quickly test the code above:

"""Testing the LocationFinder."""

__author__ = 'bmichalski@gmail.com (Brian Michalski)'

import location_finder
import mox
import StringIO
import urllib
import unittest

class TestLocationFinder(unittest.TestCase):

  def setUp(self):
    self.mox = mox.Mox()

  def tearDown(self):
    self.mox.UnsetStubs()

  def testFinder(self):
    fetch_backend = self.mox.CreateMock(urllib)
    fake_data = StringIO.StringIO((
      '{"results":[{"geometry":{"location":'
      '{"lat":37.42114440,"lng":-122.08530320}}}],'
      '"status":"OK"}'
    ))
    fetch_backend.urlopen(mox.StrContains('Amphitheatre')).AndReturn(fake_data)
    self.mox.ReplayAll()

    finder = location_finder.LocationFinder(fetch_backend)
    result = finder.find('1600 Amphitheatre Parkway, Mountain View, CA')
    self.assertEqual(result[0], 37.42114440)
    self.mox.VerifyAll()

if __name__ == '__main__':
    unittest.main()

Since urlopen returns a file-like object I use a StringIO object and hardcode some output. I could have saved the result verbatim from Google in a file and put that somewhere to return. In summary, testFinder is broken down into two halves, the first have creates a fake urllib and tells it how to work responding to the one method and the second half loads the LocationFinder using the fake backend and verifies the calls worked as expected.

My old fashioned technique would have just been to write something like:

class mockurllib(object):
  def urlopen(url):
    return something

which isn’t too bad when you’re testing just 1 function like I am above, but if you’re testing different calls to different backends with different responses it can get a bit verbose and messy. I’m sure there’s room to improve my current understanding, maybe I’ll pick up some more handy testing tricks later.

The one thing I dislike about mox is the need to include urllib at all in the test (or import in Python’s case). I think there are ways to mock it out in a more generic fashion, but that feels like it might be getting sloppy. Since urllib is being imported it could still run a potentially slow initialization sequence, not applicable in this specific case but certainly something to watch out for.

Continuous Integration Setup

I’m trying to integrate testing as much as possible into the development of Concerto 2.  We’re building in Ruby on Rails, so testing is built into the framework and language, the trick is just getting the team on board and writing tests (and not breaking them as we develop).  To help facilitate testing, I setup BigTuna to run a continuous integration server, automatically testing each version that gets pushed to our GitHub repository.

Since people could be using Ruby 1.8.7 or 1.9.2, we use RVM on testing environment to run tests in both versions.  If you’re looking to replicate our setup which updates git submodules, refreshes the bundle, migrates the database, and finally runs the tests, this set of steps might work for you.

In our setup, we have a database.yml.sample located in our project directory (in our case /bigtuna/builds/concerto) which gets copied over to each build.

rvm 1.8.7 exec bundle install --path=%project_dir%/bundle --deployment
cp %project_dir%/database.yml.sample %build_dir%/config/database.yml
git submodule init && git submodule update
rvm 1.8.7 exec env RAILS_ENV=test bundle exec rake db:migrate --trace
rvm 1.8.7 exec env RAILS_ENV=test bundle exec rake

and for our 1.9.2 tests..

rvm 1.9.2 exec bundle install --path=%project_dir%/bundle --deployment
cp %project_dir%/database.yml.sample %build_dir%/config/database.yml
git submodule init && git submodule update
rvm 1.9.2 exec env RAILS_ENV=test bundle exec rake db:migrate --trace
rvm 1.9.2 exec env RAILS_ENV=test bundle exec rake

Getting the rvm portion of the setup working has been the hardest bit, getting the right ruby environment and bundle of gems to the apps was tough.  The above config was written for rvm 1.2.8 and Bundler 1.0.12, otherwise I don’t believe there are any server-specific configuration parameters at play here.

Best of luck if you’re going the continuous integration route, hopefully you can keep your build green!