Search code examples
pythondjangounit-testingpython-mock

Mock class attribute with side_efffect


How can I raise exceptions while accessing an attribute of the mocked object?

I have tried to do the same with the following snippet,

import unittest
from unittest import TestCase
from django.core.exceptions import ObjectDoesNotExist
from unittest.mock import MagicMock, PropertyMock


def function_to_call_one_to_one(model_instance):
    try:
        return model_instance.artist.name
    except ObjectDoesNotExist:
        # calling `model_instance.artist` will raise `ObjectDoesNotExist` exception
        return "raised `ObjectDoesNotExist`"


class TestObjectDoesNotExist(TestCase):
    def test_artist_name(self):
        model_instance = MagicMock()
        model_instance.artist.name = "Michael Jackson"
        name = function_to_call_one_to_one(model_instance)
        self.assertEqual(name, "Michael Jackson")

    def test_artist_name_with_error(self):
        model_instance = MagicMock()
        model_instance.artist = PropertyMock(side_effect=ObjectDoesNotExist)
        res = function_to_call_one_to_one(model_instance)
        self.assertEqual(res, "raised `ObjectDoesNotExist`")


if __name__ == '__main__':
    unittest.main()

Unfortunately, the test function, test_artist_name_with_error(...) has failed with message,

AssertionError: <MagicMock name='mock.artist.name' id='140084482479440'> != 'raised ObjectDoesNotExist'

How can I write the unit-test for this case?

Note: I have seen this SO post, Python: Mock side_effect on object attribute, but, it doesn't work for me. I hope, this example is a reproducible one.


Solution

  • Great question. Here's one part of the docs that you might have overlooked:

    Because of the way mock attributes are stored you can’t directly attach a PropertyMock to a mock object. Instead, you can attach it to the mock type object.

    This works:

    type(model_instance).artist = PropertyMock(side_effect=ObjectDoesNotExist)