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.