Saturday, September 25, 2010

Monkey Patching in Django Tests AKA I Should've Used Mock Objects

   About a year ago I was introduced to mock objects, specifically PyMox. It seemed like a great idea, but I was new to unit tests to begin with, as well as python, and I had never even heard of mock objects. Since then I've lost a bit of my green color, but only now I'm starting to understand how mock objects work. In the meantime I've committed to unit tests and occasionally monkey patching them as ad hoc mocking. I will be the first to admit that this is not ideal, mock objects are probably the way to go. But for simple cases and just getting on with it, monkey patching may work for you.

If you aren't familiar with the term, monkey patching means changing code at runtime. Only possible in wicked languages like python, it can be a major source of headaches if you do this unexpectedly (or if somebody else does it unexpectedly)

I'm currently using Twilio for handling my phone number redirection service for mobile phones, Churp. In my unit tests I needed to make sure that my requests and responses were being handled properly, but I didn't want to do any real requests to Twilio every time I run the suite. So I monkey patched the Twilio rest client for my tests. It looks something like this:

class FakeTwilioRestClient(object):
    #this is a class variable that I use for switching the return value of the Fake Rest Client
    find_number = False
    def __init__(self, account_sid, account_token):
        # basically copy the __init__ of the real class here since this class
        # will be initialized just like the real one
        pass

    def request(self, request_resource, method_type, params=None):
        # parameters must be the same as the real method since that's how it's
        # going to be called inside your code
        if not self.find_number:
            return '{"friendly_name": "(604) 777 8856"}'
        else:
            return """{"available_phone_numbers": [{"friendly_name": "(604) 777 8856"}]}"""


Now when the view uses the client, it's been replaced by what's above, instead of doing a real request to Twilio. So that is the client, here is what the test looks like.

def test_my_twilio_view(self):
    from myviews import twilio
    original_account_class = twilio.Account
    twilio.Account = FakeTwilioClient
    .....

    #fix the monkey patching for the other tests
    twilio.Account = original_account_class


def test_my_twilio_view_2(self):
    from myviews import twilio
    original_account_class = twilio.Account
    FakeTwilioClient.find_number = True #now the fake client will return a different value
    twilio.Account = FakeTwilioClient

One thing that took me a while to really get was how to monkey patch the imports right. What won't work is this:

def test(self):
    import twilio
    twilio.Account = FakeTwilioClient

This won't patch anything. It is important  to import the module you're patching from the module you're testing, or nothing will happen and you might be scratching your head as to why (python modules have their own scope, this is why it's important to import the patched module from the right place and not from your python path)

As always, all comments and / or corrections are welcome. Part of the reason for this blog is to help me learn while trying to give back to the community that has helped me so much.

Thursday, September 23, 2010

Combining Flask and Django

In building Churp I had a need for creating a lightweight service to handle my call routing and communicating with Twilio. I had build most of the site using Django, and I could have made it a separate django app, but I wanted to decouple the presentation of the site and the routing of the calls. Flask to the rescue.

I've used Flask just a few times: once for an internal api, and now as a handler for requests coming from Twilio. One thing that is a problem, however, is writing unit tests that share a MySQL database with a separate Django application. After a lot of trial and error I managed to come up with a solution to using Django tests for your flask application. Flask has its own way of testing, but it was missing too many things for me to be able to use it i.e. completely geared towards using sqlite. In the end the advantages over writing an actual Django app are small, but Flask being smaller and easier to manage than Django, I'm happy with the result. Here is basic solution to upgrading your unit tests with Flask:



from django.core.management import setup_environ
from django.test import TestCase
from your_project import settings
setup_environ(settings)

#NOTE: you may need to add to sys.path to make sure your django project is on your path

class FlaskAppTest(TestCase):
    fixtures = ['your_fixtures.json',]


    def setUp(self):
        self.app = file_with_flask_app.app.test_client()


    def test_as_usual(self);
        response = self.app.post('/your_flask_url/', data={'your': 'post_parameters',}, 
                                              headers={'header1': 'header1_value'})
        self.assertEqual(response.status_code, 200)
        ....