Testing auto_now DateTime Fields in Django

Django's auto_now_add and auto_now field arguments provide a convenient way to create a field which tracks when an object was created and last modified.

For example:

class BlogPost(models.Model):
      title   = models.CharField()
      author  = models.ForeignKey("author")
      body    = models.TextField()
      created = models.DateTimeField(auto_now_add=True)
      edited  = models.DateTimeField(auto_now=True)
      ...

Unfortunately, they can make writing unit tests which depend on these creation or modification times difficult, as there is no simple way to set these fields to a specific time for testing.

The problem

Although auto_now fields can be be changed in code, as they will update themselves afterwards with the present date and time, they can effectively never be set to another time for testing.

For example, if your Django-powered blog is set to prevent commenting on posts a month after it was last edited, you may wish to create a post object to test the block. The following example will not work:

def test_no_comment(self):
      blog_post = BlogPostFactory()

      blog_post.edited = datetime.now() - timedelta(days=60)
      # Django will replace this change with now()

      self.assertFalse(blog_post.can_comment())

Even changes to an auto_now field in a factory or using the update() function won't last; Django will still overwrite the change with the current time.

The easiest way to fix this for testing? Fake the current time.

The solution: Mock Time

The auto_now field uses django.utils.timezone.now to obtain the current time. We can mock.patch() this function to return a false time when the factory creates the object for testing:

import mock
   ...
   def test_no_comment(self):

      # make "now" 2 months ago
      testtime = datetime.now() - timedelta(days=60)

      with mock.patch('django.utils.timezone.now') as mock_now:
         mock_now.return_value = testtime

         blog_post = BlogPostFactory()

      # out of the with statement - now is now the real now
      self.assertFalse(blog_post.can_comment())

Once you need to return to the present, get out of the with statement and then you can test the long-ago-updated object in the present time.

Other Solutions

An alternative solution is to use a fixture instead; however fixtures should generally be avoided as they have to be manually updated as your models change and can lead to tests incorrectly passing or failing - see this blog post for more details.

Another alternative is to create your own version of save() for the object which can be overridden directly. However this requires more complex code than using mock.patch() - and all that extra code will end up in production, not in the test as in the example above.