Login

safe(r) monkeypatching scheme for django testing

Author:
showell
Posted:
November 24, 2009
Language:
Python
Version:
1.1
Tags:
testing monkeypatch
Score:
0 (after 0 ratings)

In test code, it is sometimes useful to monkeypatch a Django method to have stubbed out behavior, so that you can simplify data setup. Even with decent data setup you might want to avoid execution of Django code that is not the target of your test.

The code snippet shown here illustrates a technique to limit the scope of your monkeypatches. It uses the Python "with" statement, which was introduced in 2.5.

with statement

The key aspect of the "with" machinery is that you can set up an exit method that gets called even if the code inside the "with" raises an exception. This guarantees that your monkeypatch gets un-monkeyed before any other code gets called.

I don't recommend monkeypatches in production, but if you HAVE to resort to a monkeypatch, I definitely advise using "with" to limit their scope.

The examples on the left illustrate how to suppress versions of reverse() and timesince()--look at the import statements to see which ones I am talking about. Obviously, monkeypatching is not for the faint of the heart, as you need to be able to find the code to monkeypatch in Django source, and you need to be sure there aren't decorators at play.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    from django.core import urlresolvers
    from django.utils import timesince

    # set up a class to turn off reverse via monkeypatch...we do not care about its details
    class PatchReverse:
        def __enter__(self):
            self.old_method = urlresolvers.reverse
            urlresolvers.reverse = lambda view_name, args, kwargs, current_app: 'stub'

        def __exit__(self, type, value, traceback):
            urlresolvers.reverse = self.old_method

    # nor do we care about the details of timesince...note that we monkeypatch the one
    # from django.utils...it is harder to monkeypatch template filters due to django
    # registration mechanisms
    class PatchTimeSince:
        def __enter__(self):
            self.old_method = timesince.timesince
            timesince.timesince = lambda arg: 'stub'

        def __exit__(self, type, value, traceback):
            timesince.timesince = self.old_method

    with PatchReverse():
        with PatchTimeSince():
            from django.template.loader import render_to_string
            render_to_string('activity.html', dict(...))

More like this

  1. Variable._resolve_lookup monkeypatch by showell 5 years, 9 months ago
  2. django-noserun for testing by mjt 6 years, 5 months ago
  3. Fail Faster: unsafe_test Management Command by majgis 3 years ago
  4. HttpMethodsMiddleware by hawkeye 8 years, 4 months ago
  5. testdata tag for templates by showell 6 years, 4 months ago

Comments

Please login first before commenting.