سؤال

I'd like to write a test that will help me determine whether an API of the library I'm using hasn't changed e.g. after upgrade.

If I'd create a "blind mock" object then the mock will always use the one method and tests will pass, but my the app will break with the actual library.

I know there's a way of patching existing objects:

@patch.object(ZipFile, 'namelist')
def test_my_method(self, mocked_zipfile):

which will at least check whether the namelist method actually exists on the original object, but it still allows me to make a typo when mocking the object inside:

@patch.object(ZipFile, 'namelist')
def test_my_method(self, mocked_zipfile):
    mocked_zipfile.namlist.return_value = [ 'one.txt', 'two.txt' ]

When I make a typo (namlist) inside the test and inside the tested code, the test will just silently pass.

Is there any way I can prevent monkey patching the non-existing methods of mocked object except keeping it in mind every time I write the test (which is not the best way when you have a team and you want to automatically check these things)?

هل كانت مفيدة؟

المحلول

You can patch zipfile.Zipfile with autospec=True:

If you set autospec=True then the mock with be created with a spec from the object being replaced. All attributes of the mock will also have the spec of the corresponding attribute of the object being replaced. Methods and functions being mocked will have their arguments checked and will raise a TypeError if they are called with the wrong signature. For mocks replacing a class, their return value (the ‘instance’) will have the same spec as the class.

The following test will fail due to AttributeError: Mock object has no attribute 'namlist':

from unittest import TestCase
from mock import patch

class MyTestCase(TestCase):
    @patch.object(ZipFile, 'namelist', autospec=True)
    def test_my_method(self, mocked_zipfile):
        mocked_zipfile.namlist.return_value = [ 'one.txt', 'two.txt' ]

Hope that helps.

نصائح أخرى

Have you tried using the wraps keyword argument?

This works for me:

>>> from mock import Mock
>>> import zipfile
>>> mocked_zipfile = Mock(wraps=zipfile.ZipFile)
>>> mocked_zipfile.namlist.return_value = ['one.txt', 'two.txt']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/jbaiter/.envs/spreads/local/lib/python2.7/site-packages/mock.py", line 670, in __getattr__
    wraps = getattr(self._mock_wraps, name)
AttributeError: type object 'ZipFile' has no attribute 'namlist'
>>> mocked_zipfile.namelist.return_value = ['one.txt', 'two.txt']
>>> mocked_zipfile.namelist()
['one.txt', 'two.txt']

I haven't tried with a @patch decorated method yet, but this should work:

@patch('zipfile.ZipFile', Mock(wraps=zipfile.ZipFile))
def test_my_method(self, mocked_zipfile):
    # call code that depends on ZipFile
    pass
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top