Using Mock objects in Django for testing the current date

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.




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

Comments support markdown

Comments are closed.

Comments have been close for this post.

About this post

Posted at 1:20 p.m. on August 14, 2008

Comments: 2

Tags: , , ,

Search Blog


Recent Posts

The role of designers in the Django community

5 months, 3 weeks Ago (Comments: 23)

Large Problems in Django, Mostly Solved: Documentation

5 months, 3 weeks Ago (Comments: 7)

A simple Perl IRCBot

6 months, 3 weeks Ago (Comments: 0)

More Posts...

Projects


Friends


Categories


Tag Cloud

abstract aggregator book classbased community conferences conventions core dash debugging deployment designers django djangocon doctest education eurodjangocon fixtures idea ideas iowa kong largeproblems lawrence mediaphormedia mentor middleware migrations music packaging parsing patterns pdb philosophy politics pony post-a-day postaday09 practical pretty production project projects python ramblings reusable review school screencast setuptools software solutions south sphinx ssh students talk teaching template-tags templates templatetags testing testing-series testmaker tip tips tutorial umw unittest

Archive


I may not have gone where I intended to go, but I think I have ended up where I intended to be.

- Douglas Adams