There are situations where real time is important in the behaviour of some views. For example, one could write an anti-spam mechanism to prevent robots or ill-intended users to post a form too often and too rapidly. In this article I'll present a strategy for testing such views.

Let's take a very simple example, in which we'll let users post messages. The time constraint we'll set is that users have to wait at least 15 minutes between 2 consecutive postings, otherwise their message will be ignored. I'll start by giving out the code. First, the model:

from django.db import models from django.contrib.auth.models import User class Message ( models . Model ): user = models . ForeignKey ( User , related_name = 'messages' ) datetime = models . DateTimeField () message = models . CharField ( max_length = 100 ) def __unicode__ ( self ): return self . message

Nothing complicated here. Just a simple model to record the user and the time at which the message is posted.

Then, we will use a setting to determine the interval of time a user should wait before posting another message (You will see later why using a setting is crucial here). In our example we pick 15 minutes. So, add the following to your settings.py file:

MESSAGE_INTERVAL = 60 * 15 # 15 minutes expressed in seconds

Now, on to the view:

from datetime import datetime , timedelta from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.http import HttpResponse from models import Message def send_message ( request ): message_interval = timedelta ( seconds = settings . MESSAGE_INTERVAL ) now = datetime . now () try : Message . objects . get ( user = request . user , datetime__gte = now - message_interval ) # A message has been posted too recently, so ask the user to wait a bit return HttpResponse ( 'Be patient, try again later.' ) except ObjectDoesNotExist : # The user waited long enough, so we can create the message Message . objects . create ( user = request . user , message = request . POST [ 'message' ], datetime = now ) return HttpResponse ( 'Thanks for your message.' )

It's a pretty simple view, but let me just explain what's going on in there. First, we retrieve the time interval from the settings (it should be 15 minutes, or whatever value you've set). Then we check if a message has been posted by the same user during the last 15 minutes. If there is one, return an error notice telling the user he needs to wait; otherwise save the message and return a success notice.

All good! Now, how do we go about testing this? I'll start by giving out the code:

from time import sleep from django.test import TestCase from django.conf import settings from django.contrib.auth.models import User from models import Message message_interval = 1 # seconds not_long_enough = 0.7 # seconds long_enough = 1.3 # seconds class TimeBasedTesting ( TestCase ): def setUp ( self ): self . old_MESSAGE_INTERVAL = settings . MESSAGE_INTERVAL settings . MESSAGE_INTERVAL = message_interval User . objects . create_user ( 'testuser' , 'testuser@example.com' , 'testpw' ) def tearDown ( self ): settings . MESSAGE_INTERVAL = self . old_MESSAGE_INTERVAL def test_message ( self ): self . client . login ( username = 'testuser' , password = 'testpw' ) # First message response = self . client . post ( '/send_message/' , { 'message' : 'First try!' }) self . assertEquals ( response . content , 'Thanks for your message.' ) # Wait enough sleep ( long_enough ) response = self . client . post ( '/send_message/' , { 'message' : 'Second try!' }) self . assertEquals ( response . content , 'Thanks for your message.' ) # Don't wait enough sleep ( not_long_enough ) response = self . client . post ( '/send_message/' , { 'message' : 'Third try!' }) self . assertEquals ( response . content , 'Be patient, try again later.' ) # Wait enough sleep ( long_enough ) response = self . client . post ( '/send_message/' , { 'message' : 'Fourth try!' }) self . assertEquals ( response . content , 'Thanks for your message.' ) # Check what messages have been recorded self . assertEquals ( str ( Message . objects . all ()), '[<Message: First try!>, <Message: Second try!>, <Message: Fourth try!>]' )

Something we cannot afford is wait for 15 minutes to run each test. This is exactly why we put the waiting time interval into a setting -- because then we can override it in our tests. The setUp() method first makes a backup of the setting (stored in old_MESSAGE_INTERVAL ) and then overrides it before the tests are run. The tearDown() method eventually restores the setting when the tests are complete so there's no risk to interfere with other applications or with other tests.

In this example we have set the time interval message_interval to 1 second, and we use two other variables to simulate whether the user waits long enough or not long enough between each posting. Then, to simulate the user waiting we simply use the built-in python function sleep() . It's as simple as that! This test should take approximately 5 seconds to run.

Finally, there's no problem if for some reasons you do not want to have the time interval in your settings. In fact, you can put it anywhere you like. The key is to move it to a variable out of your view so it can easily be overridden by your tests.

Hope it helps!