How do you mock a RelatedManager method in Django?
Asked Answered
D

2

9

I have a customer manager for a Django model which overrides the create method to also save some related objects:

class CustomManager(models.Manager):
    def create(self, amount, user, description):
        txn = self.get_query_set().create(user, description)
        txn.budget_transactions.create(amount)
        return txn

My question is: how do I mock the call to txn.budget_transactions.create to raise an exception?

The budget_transactions attribute of the txn object is an instance of django.db.models.fields.related.RelatedManager. Using mock.patch to mock this class doesn't work as it is declared dynamically - it can't be imported directly.

Does anyone know how to do this?

Diablerie answered 31/8, 2012 at 9:41 Comment(2)
Are you using Mock library? Do you want to use a patch context-manager or a decorator?Fluctuation
I'm happy to use the mock library if it can solve this problem. As I state above, using mock.patch in the conventional way doesn't work due to the dynamic declaration of RelatedManager classes.Diablerie
R
10

The reason that you can't just set the RelatedManager to a mock object is because django has overridden the set method on the object. So while it appears that the mock is getting set correctly because there are no complaints, it's actually silently setting budget_transactions back to the RelatedManager. So if you really need to return a mock then you'll need to override the get method that returns the RelatedManager and return a mocked object instead.

Should end up lookig somthing like:

@mock.patch('django.db.models.fields.related.ForeignRelatedObjectsDescriptor.__get__')
def test_campaign_cancel(self, mock_manager):
    mock_manager.return_value = mock.MagicMock()
    mock_manager.return_value.create = Exception('Boom!')

That being said there are many pitfalls to this approach since it will be overriding a core django method and now ALL RelatedManagers will return a mocked object. From what I have experienced so far it's probably easier to explore other options.

Regulator answered 13/11, 2014 at 1:39 Comment(2)
I had to mock django.db.models.fields.related.ReverseManyRelatedObjectsDescriptor.__set__, but same general approach.Kamat
A contemporary answer to this question can be found here: https://mcmap.net/q/1315374/-mocking-a-relatedmanager-in-django-2Hieroglyphic
R
2

RelatedManager is a descriptor and has to be mocked using unittest.PropertyMock

from unittest import patch, MagicMock, PropertyMock, TestCase


class CustomManagerTest(TestCase):
    @patch('app.managers.Transaction.budget_transactions', new_callable=PropertyMock)
    def test_custom_manager_create(self, mock_budget_transactions):
        mock_create = MagicMock()
        mock_budget_transaction.return_value.create = mock_create

        self.assertEqual(mock_create.call_count, 1)
Rapper answered 21/12, 2021 at 14:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.