4

I am having some trouble patching a class with a function and a property.

The project structure I am working with is as follows:

project
|- src
|  |- logic
|  |  |- sub_logic
|  |  |  | __init__.py
|  |  |  | cache.py
|  |  |  | manager.py
|  |  | __init__.py  
|- test
|  | test.py  

My cache file looks like this

class Cache(object):
    def __init__(self, val):
        self._val = val

    @property
    def Val(self):
        return self._val

    def other_function(self):
        return False

The manager file looks like this

from cache import Cache


class Manager(object):
    def __init__(self):
        self._cache = Cache(20)

    def do_something(self):
        if self._cache.Val != 20:
            raise ValueError(u"Val is not 20")

        return True

    def do_something_else(self):
        if self._cache.other_function():
            raise ValueError(u"Something is True")

The tests I tried to make are the following:

from unittest import TestCase
from mock import PropertyMock, patch

from logic.sub_logic.manager import Manager
from logic.sub_logic.cache import Cache


class ManagerTestCase(TestCase):

    def test_01_cache(self):
        manager = Manager()
        self.assertEqual(manager.do_something(), True)

    @patch('logic.sub_logic.manager.Cache.Val', new_callable=PropertyMock)
    def test_02_cache(self, property_mock):
        property_mock.return_value = 20
        manager = Manager()
        self.assertEqual(manager.do_something(), True)

    @patch('logic.sub_logic.manager.Cache', spec=Cache)
    def test_03_cache(self, cache_mock):
        cache_mock.other_function.return_value = True
        manager = Manager()
        with self.assertRaises(ValueError):
            manager.do_something_else()

    @patch('logic.sub_logic.manager.Cache', spec=Cache)
    def test_04_cache(self, cache_mock):
        cache_mock.other_function.return_value = True
        cache_mock.Val = PropertyMock()
        cache_mock.Val.return_value = 20

        manager = Manager()
        with self.assertRaises(ValueError):
            manager.do_something_else()
        self.assertEqual(manager.do_something(), True)

    @patch('logic.sub_logic.manager.Cache.Val', new_callable=PropertyMock)
    @patch('logic.sub_logic.manager.Cache', spec=Cache)
    def test_05_cache(self, cache_mock, property_mock):
        cache_mock.other_function.return_value = True
        property_mock.return_value = 20
        manager = Manager()
        with self.assertRaises(ValueError):
            manager.do_something_else()
        self.assertEqual(manager.do_something(), True)

    @patch('logic.sub_logic.manager.Cache', spec=Cache)
    def test_06_cache(self, cache_mock):
        cache_mock.other_function.return_value = True
        cache_mock.Val = 20

        manager = Manager()
        with self.assertRaises(ValueError):
            manager.do_something_else()
        self.assertEqual(manager.do_something(), True)

The problem is that test_04_cache and test_05_cache are not working. When debugging the the tests, the mock parameter provided is as I expected it to be. But the Manager creates a MagicMock where the property Val is not a PropertyMock but also a MagicMock.

I inspected test_06_cache in PyCharm Debugger which reports the following:

  • cache_mock.Val = {int}20
  • manager._cache.Val = {MagicMock}<MagicMock name='Cache().Val' id='61044848'>

Am I missing something? Or is it not possible?

8
  • If you're mocking out the cache, why do you care if it's really implemented as a property? Just set cache_mock.Val = 20. Commented Nov 15, 2016 at 10:49
  • You suggest replacing cache_mock.Val = PropertyMock() cache_mock.Val.return_value = 20 with just cache_mock.Val = 20? I just tried that and it seems not to work. Commented Nov 15, 2016 at 10:52
  • In test_05 you first mock Cache.Val, but then after that you mock Cache which replaces the whole object with MagicMock. This is why in test_05 you see Val as a MagicMock. Commented Nov 15, 2016 at 10:54
  • Could you expand on "seems not to work"? My point is that the fact that in the real Cache the Val is a property is irrelevant as far as the Manager is concerned, so when you're unit-testing the latter you can just use a regular attribute. Commented Nov 15, 2016 at 11:01
  • 1
    You need to set cache_mock().Val - note that you have access to the class, not the instance. Commented Nov 15, 2016 at 11:22

1 Answer 1

6

When you use

@patch('logic.sub_logic.manager.Cache', spec=Cache)

the resulting mock is for the class. Your Manager then creates an instance by calling that class, in __init__. You should therefore be setting attributes and return values on mock_cache() (note parentheses), which is the "instance" that will be assigned to manager._cache, rather than on the "class" mock_cache.

Note that, as the manager doesn't know that the cache is using a @property, you can just set:

mock_cache().Val = 20
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.