简体   繁体   中英

@mock.patch isn't raising an attribute error even after setting side_effect

I'm attempting to fix a bug in the python package caniusepython3 which arises because distlib isn't parsing pypi projects correctly. I've written this unit test

 @mock.patch('distlib.locators.locate')
 def test_blocking_dependencies_locators_fails(self, distlib_mock):
     """
     Testing the work around for //bitbucket.org/pypa/distlib/issue/59/
     """
     py3 = {'py3_project': ''}
     breaking_project = 'test_project'
     distlib_mock.locators.locate.return_value = "foo"
     distlib_mock.locators.locate.side_effect = AttributeError()
     got = dependencies.blocking_dependencies([breaking_project], py3)
     # If you'd like to test that a message is logged we can use 
     # testfixtures.LogCapture or stdout redirects.

So that when distlib fixes the error in the next release of distlib the test case will still be valid.

The problem is that the MagicMock never raises a AttributeError as I expected and instead returns a string representation of the magic mock object

try:
    # sets dist to <MagicMock name='locate()' id='4447530792'>
    dist = distlib.locators.locate(project)
except AttributeError:
    # This is a work around //bitbucket.org/pypa/distlib/issue/59/
    log.warning('{0} found but had to be skipped.'.format(project))
    continue

And causes this stack trace later on because it returns the object repr,

======================================================================
ERROR: Testing the work around for //bitbucket.org/pypa/distlib/issue/59/
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/mock.py", line 1136, in patched
    return func(*args, **keywargs)
  File "/Users/alexlord/git/caniusepython3/caniusepython3/test/test_dependencies.py", line 81, in test_blocking_dependencies_locators_fails
    got = dependencies.blocking_dependencies([breaking_project], py3)
  File "/Users/alexlord/git/caniusepython3/caniusepython3/dependencies.py", line 119, in blocking_dependencies
    return reasons_to_paths(reasons)
  File "/Users/alexlord/git/caniusepython3/caniusepython3/dependencies.py", line 43, in reasons_to_paths
    parent = reasons[blocker]
  File "/Users/alexlord/git/caniusepython3/caniusepython3/dependencies.py", line 29, in __getitem__
    return super(LowerDict, self).__getitem__(key.lower())
nose.proxy.KeyError: <MagicMock name='locate().name.lower().lower()' id='4345929400'>
-------------------- >> begin captured logging << --------------------
ciu: INFO: Checking top-level project: test_project ...
ciu: INFO: Locating <MagicMock name='locate().name.lower()' id='4344734944'>
ciu: INFO: Dependencies of <MagicMock name='locate().name.lower()' id='4344734944'>: []
--------------------- >> end captured logging << ---------------------

Why is the MagicMock not returning an exception when distlib.locator.locate() is called?

Update: I was able to get this unit test to work when I switched to using

def test_blocking_dependencies_locators_fails(self):
    """
    Testing the work around for //bitbucket.org/pypa/distlib/issue/59/
    """
    with mock.patch.object(distlib.locators, 'locate') as locate_mock:
        py3 = {'py3_project': ''}
        breaking_project = 'test_project'
        locate_mock.side_effect = AttributeError()
        got = dependencies.blocking_dependencies([breaking_project], py3)
        # If you'd like to test that a message is logged we can use 
        # testfixtures.LogCapture or stdout redirects.

But I'm still wondering what I did wrong with the decorator format.

When you use @mock.patch , it mocks what you tell it, and passes that mock object as a parameter. Thus, your distlib_mock parameter is the mock locate function. You're effectively setting attributes on distlib.locators.locate.locators.locate . Set the attributes directly on the provided mock, and things should work better.

@mock.patch('distlib.locators.locate')
def test_blocking_dependencies_locators_fails(self, locate_mock):
    # ...
    locate_mock.return_value = "foo"
    locate_mock.side_effect = AttributeError()
    # ...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM