Search code examples
pythondjangopython-unittestdjango-testing

How to freeze datetime.now for unit testing


I have a model that uses a function to return a default datetime:

class Company(models.Model):
    q1_results_date = models.DateField(
        verbose_name='Q1 financial results',
        default=quarter_results_date(1),
        blank=False,
        null=False,
    )

def quarter_results_date(month):
    return datetime.datetime(
        datetime.datetime.now().year,
        month,
        calendar.monthrange(datetime.datetime.now().year, month)[1]
    )

I want to unit test this, which requires me to set datetime.now() to a known value. To do this I am using freezegun.freeze_time:

def test_quarter_results_date(self):
    with freeze_time("2012-01-14"):
        print('check datetime.now()', datetime.now())
        c = Company.objects.create()
    ...

However, although the print statement shows 2012-01-14, the datetime is not frozen as it still uses today's date when evaluating c1.q1_results_date.

How can I correct this?


Solution

  • The reason that this will not work is because you call the function. This thus means the datetime is evaluated when the class is interpreted, so this is basically when you start the server. At that moment the freezegun is not yet active.

    This thus also means that if you later run the server for some long time, and the year gets incremented, it will still use the old value.

    You can pass a callable to the default value, and thus use a helper function for example:

    def quarter_results_date(month):
        yr = datetime.datetime.now().year
        __, dy = calendar.monthrange(yr, month)
        return datetime.datetime(
            yr,
            month,
            dy
        )
    
    def quarter_results_date_first():
        return quarter_results_date(1)
    
    class Company(models.Model):
        q1_results_date = models.DateField(
            verbose_name='Q1 financial results',
            default=quarter_results_date_first,
            blank=False,
            null=False,
        )

    Note that no parenthesis are used for the default=quarter_results_date_first, so we pass a reference to the function, not a datetime value.