« Easily packaging and distributing Django apps with setuptools and easy_install | Screencast: Debugging with the Django Error Page »
Today I ran into a fun problem when writing template tags at work. (I'll write another post later on the fun-ness that is testing of template tags :) In ellington we have some templatetags that test for the current time of day. ifmorning, ifnight and so on. These template tags are using datetime.datetime.now() to check to see if the time is within a certain range. This is impossible to test in a standard way without doing some hacking on the datetime.datetime object.
The solution is actually pretty easy. Let me warn, although this is the correct solution in this case, monkeypatching is generally BAD. You don't want to just be playing around with python or django's stdlib and breaking things for other people. With that warning, let me show you how I went about doing this.
This code is called in this fashion:
import unittest
class LoadDateutil(TemplateTestCase):
def test_load(self):
olddatetime = datetime.datetime
datetime.datetime = make_datetime(5)
self.assertEqual(self.render(u'{% load dateutil %}{% ifnight %}Hi{% endifnight %}'), u'')
datetime.datetime = olddatetime
Now let me explain what all is going on here. TemplateTestCase is an internal base class for doing templatetag tests. This will probably be released (by me or Matt Croydon) sometime soonish.
The first thing you want to make sure you do is leave everything how you found it. So before we go about editing the datetime.datetime object, we save it into olddatetime, and once we are done with the test, we return datetime.datetime back to its original value. In the middle of the test, we are calling datetime.datetime = make_datetime(5) which is returning a datetime.datetime object that has it's now() method overwritten. The argument to make_datetime is the hour of the day you want to represent.
Let's take a look at how make_datetime is working:
import datetime
def make_datetime(hour):
class MockDatetime(datetime.datetime):
@classmethod
def now(cls):
return datetime.datetime(2007, 1, 1, hour)
return MockDatetime
This code is creating the MockDatetime class, and then defining the now() method. The @classmethod decorator must be used because the now() method is a class method. Then the now() method simply returns a datetime.datetime object with the correct hour in it.
This is pretty simple, and is the correct and best way to do testing of this nature. I hope this is helpful to someone out there :) Also note that these methods are not django specific, and can be used in anything with python.
As a caveat, make sure that the templatetag code you are running this test against is importing the datetime module, and not datetime.datetime, because in that instance this code will not work (because the code we're overwriting will be re-imported in the templatetag (as far as I can tell))
Thanks to Malcolm for helping me with this.
Posted at 1:20 p.m. on August 14, 2008
Comments: 2
Tags: django , mock objects , monkey patching , testing
Mocking an External Web Service in Python
Automating tests in Django
Announcing Django Crawler and django-test-utils
Welcome to the home of Eric Holscher on the web. I talk about software development, mostly in the realm of Django. I am interested in the real time web, testing, mobile apps, and other things.
Why Read the Docs matters
1 week, 5 days ago (Comments: 7)
Read the Docs Update
9 months, 4 weeks ago (Comments: 2)
Using Reviewboard with Git
1 year ago (Comments: 0)
Read the Docs Updates
1 year ago (Comments: 1)
Handling Django Settings Files
1 year ago (Comments: 12)
Required Reading
1 year, 2 months ago (Comments: 0)
Using Haystack to index non-database content
1 year, 2 months ago (Comments: 4)
Correct commands to check out and update VCS repos
1 year, 2 months ago (Comments: 0)
Site upgrades
1 year, 2 months ago (Comments: 0)
Building a Django App Server with Chef: Part 4
1 year, 2 months ago (Comments: 1)
Setting up Django and mod_wsgi
Building a Django App Server with Chef: Part 1
Screencast: Django Command Extensions
Big list of Django tips (and some python tips too)
Handling Django Settings Files
Lessons Learned From The Dash: Easy Django Deployment
Large Problems in Django, Mostly Solved: Delayed Execution
Building a Django App Server with Chef: Part 2


Comments
1 says...
you might want to investigate mock by michael foord. It makes this kind of stuff easy.
Posted at 10:01 p.m. on August 17, 2008
2 rodrigue says...
Monkaypatching is something I find very useful in python because it makes it easy to replace calls to the standard library or to 3rd party libraries for test purpose. However, when writing new code, careful design and dependency injection can help mock things out in a clean and unobstrusive way.
Also, in your example, you may want to do add a try/finally, to ensure that datetime.datetime = olddatetime gets called whatever happens. Personally, I have a BaseTestClass with a method to replace an attribute and a custom tearDown that ensure we clean what we've modified.
Posted at 2:39 p.m. on December 9, 2008