Django unit testing with date/time-based objects Django unit testing with date/time-based objects django django

Django unit testing with date/time-based objects


EDIT: Since my answer is the accepted answer here I'm updating it to let everyone know a better way has been created in the meantime, the freezegun library: https://pypi.python.org/pypi/freezegun. I use this in all my projects when I want to influence time in tests. Have a look at it.

Original answer:

Replacing internal stuff like this is always dangerous because it can have nasty side effects. So what you indeed want, is to have the monkey patching be as local as possible.

We use Michael Foord's excellent mock library: http://www.voidspace.org.uk/python/mock/ that has a @patch decorator which patches certain functionality, but the monkey patch only lives in the scope of the testing function, and everything is automatically restored after the function runs out of its scope.

The only problem is that the internal datetime module is implemented in C, so by default you won't be able to monkey patch it. We fixed this by making our own simple implementation which can be mocked.

The total solution is something like this (the example is a validator function used within a Django project to validate that a date is in the future). Mind you I took this from a project but took out the non-important stuff, so things may not actually work when copy-pasting this, but you get the idea, I hope :)

First we define our own very simple implementation of datetime.date.today in a file called utils/date.py:

import datetimedef today():    return datetime.date.today()

Then we create the unittest for this validator in tests.py:

import datetimeimport mockfrom unittest2 import TestCasefrom django.core.exceptions import ValidationErrorfrom .. import validatorsclass ValidationTests(TestCase):    @mock.patch('utils.date.today')    def test_validate_future_date(self, today_mock):        # Pin python's today to returning the same date        # always so we can actually keep on unit testing in the future :)        today_mock.return_value = datetime.date(2010, 1, 1)        # A future date should work        validators.validate_future_date(datetime.date(2010, 1, 2))        # The mocked today's date should fail        with self.assertRaises(ValidationError) as e:            validators.validate_future_date(datetime.date(2010, 1, 1))        self.assertEquals([u'Date should be in the future.'], e.exception.messages)        # Date in the past should also fail        with self.assertRaises(ValidationError) as e:            validators.validate_future_date(datetime.date(2009, 12, 31))        self.assertEquals([u'Date should be in the future.'], e.exception.messages)

The final implementation looks like this:

from django.utils.translation import ugettext_lazy as _from django.core.exceptions import ValidationErrorfrom utils import datedef validate_future_date(value):    if value <= date.today():        raise ValidationError(_('Date should be in the future.'))

Hope this helps


You could write your own datetime module replacement class, implementing the methods and classes from datetime that you want to replace. For example:

import datetime as datetime_origclass DatetimeStub(object):    """A datetimestub object to replace methods and classes from     the datetime module.     Usage:        import sys        sys.modules['datetime'] = DatetimeStub()    """    class datetime(datetime_orig.datetime):        @classmethod        def now(cls):            """Override the datetime.now() method to return a            datetime one year in the future            """            result = datetime_orig.datetime.now()            return result.replace(year=result.year + 1)    def __getattr__(self, attr):        """Get the default implementation for the classes and methods        from datetime that are not replaced        """        return getattr(datetime_orig, attr)

Let's put this in its own module we'll call datetimestub.py

Then, at the start of your test, you can do this:

import sysimport datetimestubsys.modules['datetime'] = datetimestub.DatetimeStub()

Any subsequent import of the datetime module will then use the datetimestub.DatetimeStub instance, because when a module's name is used as a key in the sys.modules dictionary, the module will not be imported: the object at sys.modules[module_name] will be used instead.


Slight variation to Steef's solution. Rather than replacing datetime globally instead you could just replace the datetime module in just the module you are testing, e.g.:

import models # your module with the Event modelimport datetimestubmodels.datetime = datetimestub.DatetimeStub()

That way the change is much more localised during your test.