views:

31

answers:

1

I'm trying to build some unit tests against some code I have in Django that runs operations against a third party API. Specifically, I'm synchronizing some user data with MailChimp and using a library that implements the MailChimp API.

I have a custom class MailChimpAPI that essentially acts as a thin wrapper around the Python library I'm using. Here are some relevant portions of the code:

class MailChimpAPI(object):
    """
    A wrapper for implementing business logic and exception handling around
    the PyChimp API
    """
    ...

    def __init__(self, api_key, primary_list_id, merge_keys, use_secure=True, timeout=None):
        ...

        # setup api
        self.api_key = api_key
        self.api = PyChimp(self.api_key)

        ...

    ...


    def _call(self, method_name, args=[], kwargs={}):
        """Calls the PyChimp API directly"""
        result = getattr(self.api, method_name)(*args, **kwargs)
        if self.api.errorCode:
            self._raise_mailchimp_exception()
        else:
            return result

    ...

I snipped out (...) most of the code that implements the business logic etc., but the salient aspect here is that I set the api attribute to an instance of PyChimp (the third party library) in __init__(), and all of the actual calls to that library are made in the _call() function.

I'm somewhat new to unit testing, so I'm having a hard time figuring out the best way to approach this. One idea I had was to instantiate the PyChimp library outside of my class and pass it into the constructor. Another idea is in testing this I could mock the _call method so that it doesn't hit the actual library.

The number one issue I have is that, obviously, I don't want to execute any testing code against the actual API. So I'm trying to figure out the best way to "stub" or "mock" that API so that calls made to it during testing don't actually execute. Ideally, I'd like to have that mock API present as all tests in the Django test suite run. For example, I'm exploring the possibility of syncing a user to MailChimp whenever a User account is created via the post_save signal. For that reason, I obviously don't want the tests that run in the Django Authentication app to trigger the actual API either.

I was hoping Django had some global setup / teardown hooks or signals I could work with, but there doesn't seem to be anything.

Does anyone have any suggestions for how to cleanly substitute a "live" API with a pretend one during testing? Or am I doing it totally wrong?

I am quite confident I can hack my way to a solution, but it would be great if someone would be kind of enough to share some wisdom about the "best" way to approach this kind of thing.

+1  A: 

The best way to go about it is probably as you suggest, creating the api object outside and passing it to the constructor. This will allow you to replace the api class easily for testing.

You could create a MockPyChimp class, which has the same methods as the actual PyChimp api. This way you could easily reuse the same mock class across your tests.

I'm not familiar enough with python/django unit testing to be able to suggest any specific libraries to assist in this, but I would assume there exists some sort of mocking libraries or such.

Jani Hartikainen
Yeah, that makes sense, and I can see how that would be useful when testing this class out itself. I guess what's stumping me presently is perhaps more Django specific. I have other code in my application that ends up touching this class (and thus the API), so I'm trying to figure out how to globally mock out the API, not just when I'm testing this particular class.
jsdalton
+1: Python rarely requires fancy mock libraries. Duck typing means you can construct any old class that meets the minimal API required by the tests. It's often quite simple to write mock objects in Python.
S.Lott
@jsdalton if other tests use this class, you should consider mocking this class in your other tests. The reason I suggested a mocking library even though duck typing is because if a lib can create a dummy in one liner vs. having to write a whole class definition it's good in my books :)
Jani Hartikainen