How to write unit tests for functions using `timezone.now` in Django

Published

Updated

A short post explaining how to mock `django.utils.timezone.now` in unit tests.


I recently had to implement a Django model that represents a competition with a start_time field and an end_time field.

To determine whether the competition is active, I get the current time using django.utils.timezone.now() and check if it's within the time interval between start_time and end_time.

So far so good, the method looks like this:

# models.py
from django.db import models
from django.utils import timezone

class Competition(models.Model):
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()

    def is_active(self) -> bool:
        now = timezone.now()
        return now >= self.start_time and now < self.end_time

When we run the code, timezone.now() will return a datetime object representing the current time, and this is an issue because unit tests should be deterministic and always yield the same results. We cannot write tests that will yield different results depending on the time they're run at.

This is where mocking comes in: in our unit tests we will mock timezone.now() so that it returns the same datetime objects every time.

# test_models.py
from datetime import datetime, timezone
from unittest.mock import patch

from .models import Competition

# `timezone.now` will now always return the value we want
@patch(
    "django.utils.timezone.now",
    lambda: datetime(2022, 1, 15, tzinfo=timezone.utc),
)
def test_Competition_is_active():
    competition = Competition(
        start_time=datetime(2021, 12, 1, tzinfo=timezone.utc),
        end_time=datetime(2022, 1, 31, tzinfo=timezone.utc),
    )
    assert competition.is_active() is True

    competition = Competition(
        start_time=datetime(2021, 12, 1, tzinfo=timezone.utc),
        end_time=datetime(2021, 1, 31, tzinfo=timezone.utc),
    )
    assert competition.is_active() is False

Now that we don't have to worry about the runtime value of timezone.now, we can run our CI/CD pipelines with the same results on every run, and avoid unexpected failures.

© 2022 Tommaso Amici, All Rights Reserved