Testing created/updated/auto_now fields in Django
Tags: python, django, unittest, mock
Published: 2022-01-17 15:41:43

---
title: Testing created/updated/auto_now fields in Django
published: true
date: 2021-12-30 00:00:00 UTC
tags: python, django, unittest, mock

canonical_url: https://tech.serhatteker.com/post/2021-12/testing-created-auto-now-fields-django/
---

## Intro

Django's `auto_now_add` and `auto_now` model field arguments provide an easy way to create dates when an entity is created and/or last updated.


To give you more precise overview let's assume that I have a **Post** `Model` like below:


```python
# src/main/models.py
from django.utils.translation import ugettext_lazy as _

from src.core.models.abstract import TimeStampedModel


class Post(TimeStampedModel):
    title = models.CharField()
    author = models.ForeignKey("author")
    body = models.TextField()
    is_published = models.BooleanField(default=False)

    class Meta:
        verbose_name = _("Post")
        verbose_name_plural = _("Posts")

    def __repr__(self):
        return f"<Post {self.author}:{self.title}>"

    def __str__(self):
        return f"{self.author}:{self.title}"
```

And this is my `TimeStampedModel`:

```python
# src/core/models/abstract.py
from django.db import models


class TimeStampedModel(models.Model):
    """
    An abstract base class model that provides self-updating
    `created_at` and `updated_at` fields.
    """

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True
        ordering = ["-updated_at"]
        get_latest_by = "-updated_at"
```

Above I used `TimeStampedModel` as an `Abstract Model` and I always recommend to move your common fields into `Abstract Model` aligned with clean architecture and **DRY** method.

In a more common way you could write the model like below;

```python
# src/main/models.py
from django.db import models
from django.utils.translation import ugettext_lazy as _


class Post(models.Model):
    title = models.CharField()
    author = models.ForeignKey("author")
    body = models.TextField()
    is_published = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = _("Post")
        verbose_name_plural = _("Posts")

    def __repr__(self):
        return f"<Post {self.author}:{self.title}>"

    def __str__(self):
        return f"{self.author}:{self.title}"
```


## Problem

Unfortunately, `auto_now` and `auto_now_add` make writing unit tests which depend on creation or modification times difficult, since there is no simple way to set these fields to a specific time for testing.

For an example, assume you have a business rule; you're giving 7 days to authors to be able publish a blog post after creation. Maybe you want them to re-read their posts to eliminate typo or logic errors — yes, it makes no sense, I'm just making some company policy.

```python
# src/main/models.py
import datetime

from django.utils.translation import ugettext_lazy as _
from django.utils import timezone

from src.core.models.abstract import TimeStampedModel


class Post(TimeStampedModel):
    title = models.CharField()
    author = models.ForeignKey("author")
    body = models.TextField()
    is_published = models.BooleanField(default=False)

    class Meta:

        verbose_name = _("Post")
        verbose_name_plural = _("Posts")

    def __repr__(self):
        return f"<Post {self.author}:{self.title}>"

    def __str__(self):
        return f"{self.author}:{self.title}"

    def publish(self):
        """Publish a post which created >=7 days before"""
        if timezone.now() - self.created_at <= datetime.timedelta(days=7):
            self.is_published = True
            self.save()
```

As you can see we're making `is_published` attr `True` if a post created 7 days before or more.

In order to test this behavior, let's write some unittest;

```python
# src/tests/test_models.py
import pytest

from src.main.models import Post
from src.users.models import User
from src.users.tests.factories import UserFactory


@pytest.fixture
def user() -> User:
    return UserFactory()


class TestPostModel:
    def test_is_published_with_now(self, user):
        post = Post.objects.create(
            title="some-title",
            body="some-body",
            author=user,
        )

        post.publish()
        assert post.is_published is True
```

I'm using `FactoryBoy` library to create an `User` instance, however you can use default methods like `User.objects.create(...)`.

Above test will fail since the `created_at` field of the created `Post` model instance will always be equal to the time you run the tests. So there is no way to make `is_published` `True` in a test.

## Solution

Solution comes from Python's `unittest.mock` library: [Mock];

```python
# src/tests/test_models.py
import datetime
import pytest
from unittest import mock

from django.utils import timezone

from src.main.models import Post
from src.users.models import User
from src.users.tests.factories import UserFactory


@pytest.fixture
def user() -> User:
    return UserFactory()


class TestPostModel:
    time_test_now = timezone.now() - datetime.timedelta(days=60)

    @mock.patch("django.utils.timezone.now")
    def test_is_published_with_now(self, mock_now, user):
        mock_now.return_value = self.time_test_now
        post = Post.objects.create(
            title="some-title",
            body="some-body",
            author=user,
        )

        post.publish()
        assert post.is_published is True
```

We are patching the method with `mock.patch` decorator to return a specific time when the factory creates the object for testing. So with `mock` in this method *current* now will be 60 days before *actual* now.

When you run the test you'll see the test will pass.

Instead of decorator you can also use `context manager` —I don't usually use this method since it creates hard-to-read, nested methods when you mock/patch multiple stuff:


```python
# src/tests/test_models.py
import pytest
from unittest import mock

from django.utils import dateparse, timezone

from src.main.models import Post
from src.users.models import User
from src.users.tests.factories import UserFactory


@pytest.fixture
def user() -> User:
    return UserFactory()


class TestPostModel:
    def test_is_published_with_now(self, user):
        with mock.patch("django.utils.timezone.now") as mock_now:
            mock_now.return_value = dateparse.parse_datetime("2020-01-01T04:30:00Z")
            post = Post.objects.create(
                title="some-title",
                body="some-body",
                author=user,
            )

        post.publish()
        assert post.is_published is True
```

When you run the test you will see same successful result.


All done!




[Mock]: https://docs.python.org/3/library/unittest.mock.html
[Django - Abstract Base Classes]: https://docs.djangoproject.com/en/4.0/topics/db/models/#abstract-base-classes