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
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
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
This is where mocking comes in: in our unit tests we will mock
timezone.now() so that it returns
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.