Django – Mocking the save method on a model

Question:

I’m still here with my beginner questions about Django unit tests ^^

I’m trying to test that the save function of a model was called, without actually call the save.

Here is the method i want to test :

from django.db import models
from django.contrib.auth import models as auth_model
from allauth.socialaccount import models as allauth_model

class KangaUserManager(models.Manager):
    def create(self, username, email, password, last_name, first_name, request, registered=True, send_confirmation=True):
        kanga_user = KangaUser()
        kanga_user.user = auth_model.User.objects.create_user(
            username=username,
            email=email,
            password=password,
            first_name=first_name,
            last_name=last_name
        )
        kanga_user.preferred_language = translation.get_language()
        kanga_user.registered = registered
        kanga_user.save()

        return kanga_user

Here is the test

@mock.patch('model.models.KangaUser')
@mock.patch('django.contrib.auth.models.User')
def test_create(self, UserMock, KangaUserMock):
    # Mocking configuration
    # User
    UserMock.objects = mock.MagicMock()
    UserMock.objects.create_user = mock.MagicMock()
    user_return_value = User(id=1)
    UserMock.objects.create_user.return_value = user_return_value

     # Test
    kangauser_manager = models.KangaUserManager()
    kanga_user = kangauser_manager.create(self.username, self.email, self.password, self.last_name, self.first_name, self.request, self.registered, self.send_confirmation)

    # Checks
    # create_user called with good parameters
    UserMock.objects.create_user.assert_called_with(username=self.username, email=self.email, password=self.password, first_name=self.first_name, last_name=self.last_name)
    # KangaUser 
    self.assertTrue(models.KangaUser.save.called)

The first test (UserMock.objects.create_user.assert_called_with) is ok, but for the second, I always end up having “AssertionError: False is not true”

Asked By: CBrunain

||

Answers:

After some more googling, I found something that works :

@mock.patch.object(models.KangaUser, 'save')
@mock.patch('django.contrib.auth.models.User')
def test_create(self, user_class_mock, kangauser_save_mock):
    # Mocking configuration
    # User
    user_class_mock.objects = mock.MagicMock()
    user_class_mock.objects.create_user = mock.MagicMock()
    user_class_mock.objects.create_user.return_value = User(id=1)

    # Test
    kangauser_manager = models.KangaUserManager()
    kanga_user = kangauser_manager.create(self.username, self.email, self.password, self.last_name, self.first_name, self.request, self.registered, self.send_confirmation)

    # Checks
    # create_user called with good parameters
    user_class_mock.objects.create_user.assert_called_with(username=self.username, email=self.email, password=self.password, first_name=self.first_name, last_name=self.last_name)
    # KangaUser 
    self.assertTrue(kangauser_save_mock.called)

So, the trick was to use @mock.patch.object !

Answered By: CBrunain

With pytest-mock you can do:

def test_create(self, mocker):
    mocker.patch("path_to_the_kanga_user_manager.KangaUser.save")

Answered By: Ange Tresca

@CBrunain’s answer got me started on the right path, particularly with mock.patch.object(models.KangaUser, 'save'). In my case, however, my unit test still needed Model.save to function how it’s supposed to.

I ended up finding the wraps parameter:

wraps: Item for the mock object to wrap. If wraps is not None then
calling the Mock will pass the call through to the wrapped object
(returning the real result). Attribute access on the mock will return
a Mock object that wraps the corresponding attribute of the wrapped
object (so attempting to access an attribute that doesn’t exist will
raise an AttributeError).

In other words, you can mock save without overriding its functionality just for the purpose of checking how many times it’s been called:
@mock.patch.object(models.KangaUser, 'save', wraps=models.KangaUser.save)

To give the full answer, the code below is the same as @CBrunain’s but with the adjusted mock and an additional Mock.call_count example:

@mock.patch.object(models.KangaUser, 'save', wraps=models.KangaUser.save)
@mock.patch('django.contrib.auth.models.User')
def test_create(self, user_class_mock, kangauser_save_mock):
    # Mocking configuration
    # User
    user_class_mock.objects = mock.MagicMock()
    user_class_mock.objects.create_user = mock.MagicMock()
    user_class_mock.objects.create_user.return_value = User(id=1)

    # Test
    kangauser_manager = models.KangaUserManager()
    kanga_user = kangauser_manager.create(self.username, self.email, self.password, self.last_name, self.first_name, self.request, self.registered, self.send_confirmation)

    # Checks
    # create_user called with good parameters
    user_class_mock.objects.create_user.assert_called_with(username=self.username, email=self.email, password=self.password, first_name=self.first_name, last_name=self.last_name)
    # KangaUser 
    self.assertTrue(kangauser_save_mock.called)

    self.assertEqual(1, kangauser_save_mock.call_count)
Answered By: Drew