diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da57959 --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg* + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +# Sphinx documentation +docs/_build/ + +# Virtual Envs +.env* + +_pytest_mock_version.py diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..46396d4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,25 @@ +language: python +python: + - "3.5" + - "3.6-dev" + +install: + - pip install tox coveralls + +script: + - if [[ $TRAVIS_PYTHON_VERSION != 3.6-dev ]]; then tox; fi + - if [[ $TRAVIS_PYTHON_VERSION == 3.6-dev ]]; then tox -e py36-pytest28,py36-pytest29,py36-pytest30; fi + +after_success: + - coveralls + +deploy: + provider: pypi + user: nicoddemus + password: + secure: bB4adUZVIkt31cmNklskyIDNehujKToGnStnlunp7P8CBF6CGeNqkYU17emAPvfZbTb/ClUpiO9r6AD1ej32Uyr+I8qUyhuYtHG3JGp+WRR/tw+ytAZIJ9i+PMjBv1RAdyLENJ/Tx0LKHKsABr8dQIieLFqKZJuT77f/5ZkvI/U= + on: + tags: true + distributions: sdist bdist_wheel + repo: pytest-dev/pytest-mock + condition: $TRAVIS_PYTHON_VERSION = 3.5 diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..09beec4 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,214 @@ +1.3 +--- + +* Add support for Python 3.6. Thanks `@hackebrot`_ for the report (`#59`_). + +* ``mock.mock_open`` is now aliased as ``mocker.mock_open`` for convenience. + Thanks `@pokidovea`_ for the PR (`#66`_). + +.. _@hackebrot: https://github.com/hackebrot +.. _@pokidovea: https://github.com/pokidovea +.. _#59: https://github.com/pytest-dev/pytest-mock/issues/59 +.. _#66: https://github.com/pytest-dev/pytest-mock/pull/66 + +1.2 +--- + +* Try to import ``mock`` first instead of ``unittest.mock``. This gives the user flexibility + to install a newer ``mock`` version from PyPI instead of using the one available in the + Python distribution. + Thanks `@wcooley`_ for the PR (`#54`_). + +* ``mock.sentinel`` is now aliased as ``mocker.sentinel`` for convenience. + Thanks `@kjwilcox`_ for the PR (`#56`_). + +.. _@wcooley: https://github.com/wcooley +.. _@kjwilcox: https://github.com/kjwilcox +.. _#54: https://github.com/pytest-dev/pytest-mock/issues/54 +.. _#56: https://github.com/pytest-dev/pytest-mock/pull/56 + +1.1 +--- + +* From this version onward, ``pytest-mock`` is licensed under the `MIT`_ license (`#45`_). + +* Now the plugin also adds introspection information on differing call arguments when + calling helper methods such as ``assert_called_once_with``. The extra introspection + information is similar to pytest's and can be disabled with the ``mock_traceback_monkeypatch`` + option. + Thanks `@asfaltboy`_ for the PR (`#36`_). + +* ``mocker.stub()`` now allows passing in the name for the constructed `Mock + `_ + object instead of having to set it using the internal ``_mock_name`` attribute + directly. This is useful for debugging as the name is used in the mock's + ``repr`` string as well as related assertion failure messages. + Thanks `@jurko-gospodnetic`_ for the PR (`#40`_). + +* Monkey patching ``mock`` module for friendlier tracebacks is automatically disabled + with the ``--tb=native`` option. The underlying + mechanism used to suppress traceback entries from ``mock`` module does not work with that option + anyway plus it generates confusing messages on Python 3.5 due to exception chaining (`#44`_). + Thanks `@blueyed`_ for the report. + +* ``mock.call`` is now aliased as ``mocker.call`` for convenience. + Thanks `@jhermann`_ for the PR (`#49`_). + +.. _@jurko-gospodnetic: https://github.com/jurko-gospodnetic +.. _@asfaltboy: https://github.com/asfaltboy +.. _@jhermann: https://github.com/jhermann +.. _#45: https://github.com/pytest-dev/pytest-mock/issues/45 +.. _#36: https://github.com/pytest-dev/pytest-mock/issues/36 +.. _#40: https://github.com/pytest-dev/pytest-mock/issues/40 +.. _#44: https://github.com/pytest-dev/pytest-mock/issues/44 +.. _#49: https://github.com/pytest-dev/pytest-mock/issues/49 +.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE + + +1.0 +--- + +* Fix AttributeError with ``mocker.spy`` when spying on inherited methods + (`#42`_). Thanks `@blueyed`_ for the PR. + +.. _@blueyed: https://github.com/blueyed +.. _#42: https://github.com/pytest-dev/pytest-mock/issues/42 + +0.11.0 +------ + +* `PropertyMock `_ + is now accessible from ``mocker``. + Thanks `@satyrius`_ for the PR (`#32`_). + +* Fix regression using one of the ``assert_*`` methods in patched + functions which receive a parameter named ``method``. + Thanks `@sagarchalise`_ for the report (`#31`_). + +.. _@sagarchalise: https://github.com/sagarchalise +.. _@satyrius: https://github.com/satyrius +.. _#31: https://github.com/pytest-dev/pytest-mock/issues/31 +.. _#32: https://github.com/pytest-dev/pytest-mock/issues/32 + +0.10.1 +------ + +* Fix regression in frozen tests due to ``distutils`` import dependency. + Thanks `@The-Compiler`_ for the report (`#29`_). + +* Fix regression when using ``pytest-mock`` with ``pytest-2.7.X``. + Thanks `@akscram`_ for the report (`#28`_). + +.. _@akscram: https://github.com/Chronial +.. _#28: https://github.com/pytest-dev/pytest-mock/issues/28 +.. _#29: https://github.com/pytest-dev/pytest-mock/issues/29 + +0.10 +---- + +* ``pytest-mock`` now monkeypatches the ``mock`` library to improve pytest output + for failures of mock call assertions like ``Mock.assert_called_with()``. + Thanks to `@Chronial`_ for idea and PR (`#26`_, `#27`_)! + +.. _@Chronial: https://github.com/Chronial +.. _#26: https://github.com/pytest-dev/pytest-mock/issues/26 +.. _#27: https://github.com/pytest-dev/pytest-mock/issues/27 + +0.9.0 +----- + +* New ``mocker.resetall`` function, which calls ``reset_mock()`` in all mocked + objects up to that point. Thanks to `@mathrick`_ for the PR! + +0.8.1 +----- + +* ``pytest-mock`` is now also available as a wheel. Thanks `@rouge8`_ for the PR! + +0.8.0 +----- + +* ``mock.ANY`` is now accessible from the mocker fixture (`#17`_), thanks `@tigarmo`_ for the PR! + +.. _#17: https://github.com/pytest-dev/pytest-qt/issues/17 + +0.7.0 +----- + +Thanks to `@fogo`_, mocker.spy can now prey upon staticmethods and classmethods. :smile: + +0.6.0 +----- + +* Two new auxiliary methods, ``spy`` and ``stub``. See ``README`` for usage. + (Thanks `@fogo`_ for complete PR!) + + +0.5.0 +----- + +* ``Mock`` and ``MagicMock`` are now accessible from the ``mocker`` fixture, + many thanks to `@marcwebbie`_ for the complete PR! + +0.4.3 +----- + +* ``mocker`` fixture now returns the same object (`#8`_). Many thanks to `@RonnyPfannschmidt`_ for the PR! + +.. _#8: https://github.com/pytest-dev/pytest-qt/issues/8 + +0.4.2 +----- + +* Small fix, no longer using wheel as an alternate package since it + conditionally depends on mock module based on Python version, + as Python >= 3.3 already includes ``unittest.mock``. + Many thanks to `@The-Compiler`_ for letting me know and providing a PR with the fix! + +0.4.1 +----- + +* Small release that just uses ``pytest_mock`` as the name of the plugin, + instead of ``pytest-mock``: this makes it simple to depend on this plugin + explicitly using ``pytest_plugins`` module variable mechanism. + +0.4.0 +----- + +* Changed fixture name from ``mock`` into ``mocker`` because it conflicted + with the actual mock module, which made using it awkward when access to both + the module and the fixture were required within a test. + + Thanks `@kmosher`_ for request and discussion in `#4`_. :smile: + +.. _#4: https://github.com/pytest-dev/pytest-qt/issues/4 + + +0.3.0 +----- + +* Fixed bug `#2`_, where a patch would not be uninstalled correctly after + patching the same object twice. + +0.2.0 +----- + +* Added ``patch.dict`` support. + +0.1.0 +----- + +First release. + +.. _#2: https://github.com/pytest-dev/pytest-qt/issues/2 + +.. _@mathrick: https://github.com/mathrick +.. _@tigarmo: https://github.com/tigarmo +.. _@rouge8: https://github.com/rouge8 +.. _@fogo: https://github.com/fogo +.. _@marcwebbie: https://github.com/marcwebbie +.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt +.. _@The-Compiler: https://github.com/The-Compiler +.. _@kmosher: https://github.com/kmosher + + diff --git a/LICENSE b/LICENSE index 72a8093..e0550cd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) [2016] [Bruno Oliveira] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +MIT License + +Copyright (c) [2016] [Bruno Oliveira] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index acfb84e..74215c3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ -include README.md +include README.md include LICENSE \ No newline at end of file diff --git a/PKG-INFO b/PKG-INFO index 8733eaf..653efb9 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,282 +1,284 @@ -Metadata-Version: 1.1 -Name: pytest-mock -Version: 1.2 -Summary: Thin-wrapper around the mock package for easier use with py.test -Home-page: https://github.com/pytest-dev/pytest-mock/ -Author: Bruno Oliveira -Author-email: nicoddemus@gmail.com -License: MIT -Description: =========== - pytest-mock - =========== - - This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API - provided by the `mock package `_, - but with the benefit of not having to worry about undoing patches at the end - of a test: - - .. code-block:: python - - - def test_unix_fs(mocker): - mocker.patch('os.remove') - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - - .. Using PNG badges because PyPI doesn't support SVG - - |python| |version| |downloads| |ci| |appveyor| |coverage| - - .. |version| image:: http://img.shields.io/pypi/v/pytest-mock.png - :target: https://pypi.python.org/pypi/pytest-mock - - .. |downloads| image:: http://img.shields.io/pypi/dm/pytest-mock.png - :target: https://pypi.python.org/pypi/pytest-mock - - .. |ci| image:: http://img.shields.io/travis/pytest-dev/pytest-mock.png - :target: https://travis-ci.org/pytest-dev/pytest-mock - - .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/pid1t7iuwhkm9eh6/branch/master?svg=true - :target: https://ci.appveyor.com/project/pytestbot/pytest-mock - - .. |coverage| image:: http://img.shields.io/coveralls/pytest-dev/pytest-mock.png - :target: https://coveralls.io/r/pytest-dev/pytest-mock - - .. |python| image:: https://img.shields.io/pypi/pyversions/pytest-mock.svg - :target: https://pypi.python.org/pypi/pytest-mock/ - - Usage - ===== - - The ``mocker`` fixture has the same API as - `mock.patch `_, - supporting the same arguments: - - .. code-block:: python - - def test_foo(mocker): - # all valid calls - mocker.patch('os.remove') - mocker.patch.object(os, 'listdir', autospec=True) - mocked_isfile = mocker.patch('os.path.isfile') - - The supported methods are: - - * ``mocker.patch``: see http://www.voidspace.org.uk/python/mock/patch.html#patch. - * ``mocker.patch.object``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-object. - * ``mocker.patch.multiple``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-multiple. - * ``mocker.patch.dict``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-dict. - * ``mocker.stopall()``: stops all active patches up to this point. - * ``mocker.resetall()``: calls ``reset_mock()`` in all mocked objects up to this point. - - Some objects from the ``mock`` module are accessible directly from ``mocker`` for convenience: - - * `Mock `_ - * `MagicMock `_ - * `PropertyMock `_ - * `ANY `_ - * `call `_ *(Version 1.1)* - * `sentinel `_ *(Version 1.2)* - - - Spy - --- - - The spy acts exactly like the original method in all cases, except it allows use of `mock` - features with it, like retrieving call count. It also works for class and static methods. - - - .. code-block:: python - - def test_spy(mocker): - class Foo(object): - def bar(self): - return 42 - - foo = Foo() - mocker.spy(foo, 'bar') - assert foo.bar() == 42 - assert foo.bar.call_count == 1 - - Stub - ---- - - - The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance. - May be passed a name to be used by the constructed stub object in its repr (useful for debugging). - - .. code-block:: python - - def test_stub(mocker): - def foo(on_something): - on_something('foo', 'bar') - - stub = mocker.stub(name='on_something_stub') - - foo(stub) - stub.assert_called_once_with('foo', 'bar') - - - Improved reporting of mock call assertion errors - ------------------------------------------------ - - - This plugin monkeypatches the mock library to improve pytest output for failures - of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback - entries from the ``mock`` module. - - It also adds introspection information on differing call arguments when - calling the helper methods. This features catches `AssertionError` raised in - the method, and uses py.test's own `advanced assertions`_ to return a better - diff:: - - - m = mocker.patch.object(DS, 'create_char') - DS().create_char('Raistlin', class_='mag', gift=12) - > m.assert_called_once_with('Raistlin', class_='mage', gift=12) - E assert {'class_': 'mag', 'gift': 12} == {'class_': 'mage', 'gift': 12} - E Omitting 1 identical items, use -v to show - E Differing items: - E {'class_': 'mag'} != {'class_': 'mage'} - E Use -v to get the full diff - - - This is useful when asserting mock calls with many/nested arguments and trying - to quickly see the difference. - - This feature is probably safe, but if you encounter any problems it can be disabled in - your ``pytest.ini`` file: - - .. code-block:: ini - - [pytest] - mock_traceback_monkeypatch = false - - Note that this feature is automatically disabled with the ``--tb=native`` option. The underlying - mechanism used to suppress traceback entries from ``mock`` module does not work with that option - anyway plus it generates confusing messages on Python 3.5 due to exception chaining - - .. _advanced assertions: https://pytest.org/latest/assert.html - - - Requirements - ============ - - * Python 2.6+, Python 3.3+ - * pytest - * mock (for Python 2) - - - Install - ======= - - Install using `pip `_: - - .. code-block:: console - - $ pip install pytest-mock - - Changelog - ========= - - Please consult the `changelog page`_. - - .. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst - - Why bother with a plugin? - ========================= - - There are a number of different ``patch`` usages in the standard ``mock`` API, - but IMHO they don't scale very well when you have more than one or two - patches to apply. - - It may lead to an excessive nesting of ``with`` statements, breaking the flow - of the test: - - .. code-block:: python - - import mock - - def test_unix_fs(): - with mock.patch('os.remove'): - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - with mock.patch('os.listdir'): - assert UnixFS.ls('dir') == expected - # ... - - with mock.patch('shutil.copy'): - UnixFS.cp('src', 'dst') - # ... - - - One can use ``patch`` as a decorator to improve the flow of the test: - - .. code-block:: python - - @mock.patch('os.remove') - @mock.patch('os.listdir') - @mock.patch('shutil.copy') - def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove): - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - assert UnixFS.ls('dir') == expected - # ... - - UnixFS.cp('src', 'dst') - # ... - - But this poses a few disadvantages: - - - test functions must receive the mock objects as parameter, even if you don't plan to - access them directly; also, order depends on the order of the decorated ``patch`` - functions; - - receiving the mocks as parameters doesn't mix nicely with pytest's approach of - naming fixtures as parameters, or ``pytest.mark.parametrize``; - - you can't easily undo the mocking during the test execution; - - - **Note** - - Although mocker's API is intentionally the same as ``mock.patch``'s, its uses as context managers and function decorators are **not** supported. The purpose of this plugin is to make the use of context managers and function decorators for mocking unnecessary. Indeed, trying to use the functionality in ``mocker`` in this manner can lead to non-intuitive errors: - - .. code-block:: python - - def test_context_manager(mocker): - a = A() - with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): - assert a.doIt() == True - - .. code-block:: console - - ================================== FAILURES =================================== - ____________________________ test_context_manager _____________________________ - in test_context_manager - with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): - E AttributeError: __exit__ - - - License - ======= - - Distributed under the terms of the `MIT`_ license. - - .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE - -Keywords: pytest mock -Platform: any -Classifier: Development Status :: 5 - Production/Stable -Classifier: Framework :: Pytest -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 2.6 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Topic :: Software Development :: Testing +Metadata-Version: 1.1 +Name: pytest-mock +Version: 1.3.0 +Summary: Thin-wrapper around the mock package for easier use with py.test +Home-page: https://github.com/pytest-dev/pytest-mock/ +Author: Bruno Oliveira +Author-email: nicoddemus@gmail.com +License: MIT +Description: =========== + pytest-mock + =========== + + This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API + provided by the `mock package `_, + but with the benefit of not having to worry about undoing patches at the end + of a test: + + .. code-block:: python + + + def test_unix_fs(mocker): + mocker.patch('os.remove') + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + + .. Using PNG badges because PyPI doesn't support SVG + + |python| |version| |downloads| |ci| |appveyor| |coverage| + + .. |version| image:: http://img.shields.io/pypi/v/pytest-mock.png + :target: https://pypi.python.org/pypi/pytest-mock + + .. |downloads| image:: http://img.shields.io/pypi/dm/pytest-mock.png + :target: https://pypi.python.org/pypi/pytest-mock + + .. |ci| image:: http://img.shields.io/travis/pytest-dev/pytest-mock.png + :target: https://travis-ci.org/pytest-dev/pytest-mock + + .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/pid1t7iuwhkm9eh6/branch/master?svg=true + :target: https://ci.appveyor.com/project/pytestbot/pytest-mock + + .. |coverage| image:: http://img.shields.io/coveralls/pytest-dev/pytest-mock.png + :target: https://coveralls.io/r/pytest-dev/pytest-mock + + .. |python| image:: https://img.shields.io/pypi/pyversions/pytest-mock.svg + :target: https://pypi.python.org/pypi/pytest-mock/ + + Usage + ===== + + The ``mocker`` fixture has the same API as + `mock.patch `_, + supporting the same arguments: + + .. code-block:: python + + def test_foo(mocker): + # all valid calls + mocker.patch('os.remove') + mocker.patch.object(os, 'listdir', autospec=True) + mocked_isfile = mocker.patch('os.path.isfile') + + The supported methods are: + + * ``mocker.patch``: see http://www.voidspace.org.uk/python/mock/patch.html#patch. + * ``mocker.patch.object``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-object. + * ``mocker.patch.multiple``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-multiple. + * ``mocker.patch.dict``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-dict. + * ``mocker.stopall()``: stops all active patches up to this point. + * ``mocker.resetall()``: calls ``reset_mock()`` in all mocked objects up to this point. + + Some objects from the ``mock`` module are accessible directly from ``mocker`` for convenience: + + * `Mock `_ + * `MagicMock `_ + * `PropertyMock `_ + * `ANY `_ + * `call `_ *(Version 1.1)* + * `sentinel `_ *(Version 1.2)* + * `mock_open `_ + + + Spy + --- + + The spy acts exactly like the original method in all cases, except it allows use of `mock` + features with it, like retrieving call count. It also works for class and static methods. + + + .. code-block:: python + + def test_spy(mocker): + class Foo(object): + def bar(self): + return 42 + + foo = Foo() + mocker.spy(foo, 'bar') + assert foo.bar() == 42 + assert foo.bar.call_count == 1 + + Stub + ---- + + + The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance. + May be passed a name to be used by the constructed stub object in its repr (useful for debugging). + + .. code-block:: python + + def test_stub(mocker): + def foo(on_something): + on_something('foo', 'bar') + + stub = mocker.stub(name='on_something_stub') + + foo(stub) + stub.assert_called_once_with('foo', 'bar') + + + Improved reporting of mock call assertion errors + ------------------------------------------------ + + + This plugin monkeypatches the mock library to improve pytest output for failures + of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback + entries from the ``mock`` module. + + It also adds introspection information on differing call arguments when + calling the helper methods. This features catches `AssertionError` raised in + the method, and uses py.test's own `advanced assertions`_ to return a better + diff:: + + + m = mocker.patch.object(DS, 'create_char') + DS().create_char('Raistlin', class_='mag', gift=12) + > m.assert_called_once_with('Raistlin', class_='mage', gift=12) + E assert {'class_': 'mag', 'gift': 12} == {'class_': 'mage', 'gift': 12} + E Omitting 1 identical items, use -v to show + E Differing items: + E {'class_': 'mag'} != {'class_': 'mage'} + E Use -v to get the full diff + + + This is useful when asserting mock calls with many/nested arguments and trying + to quickly see the difference. + + This feature is probably safe, but if you encounter any problems it can be disabled in + your ``pytest.ini`` file: + + .. code-block:: ini + + [pytest] + mock_traceback_monkeypatch = false + + Note that this feature is automatically disabled with the ``--tb=native`` option. The underlying + mechanism used to suppress traceback entries from ``mock`` module does not work with that option + anyway plus it generates confusing messages on Python 3.5 due to exception chaining + + .. _advanced assertions: https://pytest.org/latest/assert.html + + + Requirements + ============ + + * Python 2.6+, Python 3.3+ + * pytest + * mock (for Python 2) + + + Install + ======= + + Install using `pip `_: + + .. code-block:: console + + $ pip install pytest-mock + + Changelog + ========= + + Please consult the `changelog page`_. + + .. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst + + Why bother with a plugin? + ========================= + + There are a number of different ``patch`` usages in the standard ``mock`` API, + but IMHO they don't scale very well when you have more than one or two + patches to apply. + + It may lead to an excessive nesting of ``with`` statements, breaking the flow + of the test: + + .. code-block:: python + + import mock + + def test_unix_fs(): + with mock.patch('os.remove'): + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + with mock.patch('os.listdir'): + assert UnixFS.ls('dir') == expected + # ... + + with mock.patch('shutil.copy'): + UnixFS.cp('src', 'dst') + # ... + + + One can use ``patch`` as a decorator to improve the flow of the test: + + .. code-block:: python + + @mock.patch('os.remove') + @mock.patch('os.listdir') + @mock.patch('shutil.copy') + def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove): + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + assert UnixFS.ls('dir') == expected + # ... + + UnixFS.cp('src', 'dst') + # ... + + But this poses a few disadvantages: + + - test functions must receive the mock objects as parameter, even if you don't plan to + access them directly; also, order depends on the order of the decorated ``patch`` + functions; + - receiving the mocks as parameters doesn't mix nicely with pytest's approach of + naming fixtures as parameters, or ``pytest.mark.parametrize``; + - you can't easily undo the mocking during the test execution; + + + **Note** + + Although mocker's API is intentionally the same as ``mock.patch``'s, its uses as context managers and function decorators are **not** supported. The purpose of this plugin is to make the use of context managers and function decorators for mocking unnecessary. Indeed, trying to use the functionality in ``mocker`` in this manner can lead to non-intuitive errors: + + .. code-block:: python + + def test_context_manager(mocker): + a = A() + with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): + assert a.doIt() == True + + .. code-block:: console + + ================================== FAILURES =================================== + ____________________________ test_context_manager _____________________________ + in test_context_manager + with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): + E AttributeError: __exit__ + + + License + ======= + + Distributed under the terms of the `MIT`_ license. + + .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE + +Keywords: pytest mock +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Framework :: Pytest +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Topic :: Software Development :: Testing diff --git a/README.rst b/README.rst index 31a6bc6..bc87fa4 100644 --- a/README.rst +++ b/README.rst @@ -1,259 +1,260 @@ -=========== -pytest-mock -=========== - -This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API -provided by the `mock package `_, -but with the benefit of not having to worry about undoing patches at the end -of a test: - -.. code-block:: python - - - def test_unix_fs(mocker): - mocker.patch('os.remove') - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - -.. Using PNG badges because PyPI doesn't support SVG - -|python| |version| |downloads| |ci| |appveyor| |coverage| - -.. |version| image:: http://img.shields.io/pypi/v/pytest-mock.png - :target: https://pypi.python.org/pypi/pytest-mock - -.. |downloads| image:: http://img.shields.io/pypi/dm/pytest-mock.png - :target: https://pypi.python.org/pypi/pytest-mock - -.. |ci| image:: http://img.shields.io/travis/pytest-dev/pytest-mock.png - :target: https://travis-ci.org/pytest-dev/pytest-mock - -.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/pid1t7iuwhkm9eh6/branch/master?svg=true - :target: https://ci.appveyor.com/project/pytestbot/pytest-mock - -.. |coverage| image:: http://img.shields.io/coveralls/pytest-dev/pytest-mock.png - :target: https://coveralls.io/r/pytest-dev/pytest-mock - -.. |python| image:: https://img.shields.io/pypi/pyversions/pytest-mock.svg - :target: https://pypi.python.org/pypi/pytest-mock/ - -Usage -===== - -The ``mocker`` fixture has the same API as -`mock.patch `_, -supporting the same arguments: - -.. code-block:: python - - def test_foo(mocker): - # all valid calls - mocker.patch('os.remove') - mocker.patch.object(os, 'listdir', autospec=True) - mocked_isfile = mocker.patch('os.path.isfile') - -The supported methods are: - -* ``mocker.patch``: see http://www.voidspace.org.uk/python/mock/patch.html#patch. -* ``mocker.patch.object``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-object. -* ``mocker.patch.multiple``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-multiple. -* ``mocker.patch.dict``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-dict. -* ``mocker.stopall()``: stops all active patches up to this point. -* ``mocker.resetall()``: calls ``reset_mock()`` in all mocked objects up to this point. - -Some objects from the ``mock`` module are accessible directly from ``mocker`` for convenience: - -* `Mock `_ -* `MagicMock `_ -* `PropertyMock `_ -* `ANY `_ -* `call `_ *(Version 1.1)* -* `sentinel `_ *(Version 1.2)* - - -Spy ---- - -The spy acts exactly like the original method in all cases, except it allows use of `mock` -features with it, like retrieving call count. It also works for class and static methods. - - -.. code-block:: python - - def test_spy(mocker): - class Foo(object): - def bar(self): - return 42 - - foo = Foo() - mocker.spy(foo, 'bar') - assert foo.bar() == 42 - assert foo.bar.call_count == 1 - -Stub ----- - - -The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance. -May be passed a name to be used by the constructed stub object in its repr (useful for debugging). - -.. code-block:: python - - def test_stub(mocker): - def foo(on_something): - on_something('foo', 'bar') - - stub = mocker.stub(name='on_something_stub') - - foo(stub) - stub.assert_called_once_with('foo', 'bar') - - -Improved reporting of mock call assertion errors ------------------------------------------------- - - -This plugin monkeypatches the mock library to improve pytest output for failures -of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback -entries from the ``mock`` module. - -It also adds introspection information on differing call arguments when -calling the helper methods. This features catches `AssertionError` raised in -the method, and uses py.test's own `advanced assertions`_ to return a better -diff:: - - - m = mocker.patch.object(DS, 'create_char') - DS().create_char('Raistlin', class_='mag', gift=12) - > m.assert_called_once_with('Raistlin', class_='mage', gift=12) - E assert {'class_': 'mag', 'gift': 12} == {'class_': 'mage', 'gift': 12} - E Omitting 1 identical items, use -v to show - E Differing items: - E {'class_': 'mag'} != {'class_': 'mage'} - E Use -v to get the full diff - - -This is useful when asserting mock calls with many/nested arguments and trying -to quickly see the difference. - -This feature is probably safe, but if you encounter any problems it can be disabled in -your ``pytest.ini`` file: - -.. code-block:: ini - - [pytest] - mock_traceback_monkeypatch = false - -Note that this feature is automatically disabled with the ``--tb=native`` option. The underlying -mechanism used to suppress traceback entries from ``mock`` module does not work with that option -anyway plus it generates confusing messages on Python 3.5 due to exception chaining - -.. _advanced assertions: https://pytest.org/latest/assert.html - - -Requirements -============ - -* Python 2.6+, Python 3.3+ -* pytest -* mock (for Python 2) - - -Install -======= - -Install using `pip `_: - -.. code-block:: console - - $ pip install pytest-mock - -Changelog -========= - -Please consult the `changelog page`_. - -.. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst - -Why bother with a plugin? -========================= - -There are a number of different ``patch`` usages in the standard ``mock`` API, -but IMHO they don't scale very well when you have more than one or two -patches to apply. - -It may lead to an excessive nesting of ``with`` statements, breaking the flow -of the test: - -.. code-block:: python - - import mock - - def test_unix_fs(): - with mock.patch('os.remove'): - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - with mock.patch('os.listdir'): - assert UnixFS.ls('dir') == expected - # ... - - with mock.patch('shutil.copy'): - UnixFS.cp('src', 'dst') - # ... - - -One can use ``patch`` as a decorator to improve the flow of the test: - -.. code-block:: python - - @mock.patch('os.remove') - @mock.patch('os.listdir') - @mock.patch('shutil.copy') - def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove): - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - assert UnixFS.ls('dir') == expected - # ... - - UnixFS.cp('src', 'dst') - # ... - -But this poses a few disadvantages: - -- test functions must receive the mock objects as parameter, even if you don't plan to - access them directly; also, order depends on the order of the decorated ``patch`` - functions; -- receiving the mocks as parameters doesn't mix nicely with pytest's approach of - naming fixtures as parameters, or ``pytest.mark.parametrize``; -- you can't easily undo the mocking during the test execution; - - -**Note** - -Although mocker's API is intentionally the same as ``mock.patch``'s, its uses as context managers and function decorators are **not** supported. The purpose of this plugin is to make the use of context managers and function decorators for mocking unnecessary. Indeed, trying to use the functionality in ``mocker`` in this manner can lead to non-intuitive errors: - -.. code-block:: python - - def test_context_manager(mocker): - a = A() - with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): - assert a.doIt() == True - -.. code-block:: console - - ================================== FAILURES =================================== - ____________________________ test_context_manager _____________________________ - in test_context_manager - with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): - E AttributeError: __exit__ - - -License -======= - -Distributed under the terms of the `MIT`_ license. - -.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE +=========== +pytest-mock +=========== + +This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API +provided by the `mock package `_, +but with the benefit of not having to worry about undoing patches at the end +of a test: + +.. code-block:: python + + + def test_unix_fs(mocker): + mocker.patch('os.remove') + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + +.. Using PNG badges because PyPI doesn't support SVG + +|python| |version| |downloads| |ci| |appveyor| |coverage| + +.. |version| image:: http://img.shields.io/pypi/v/pytest-mock.png + :target: https://pypi.python.org/pypi/pytest-mock + +.. |downloads| image:: http://img.shields.io/pypi/dm/pytest-mock.png + :target: https://pypi.python.org/pypi/pytest-mock + +.. |ci| image:: http://img.shields.io/travis/pytest-dev/pytest-mock.png + :target: https://travis-ci.org/pytest-dev/pytest-mock + +.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/pid1t7iuwhkm9eh6/branch/master?svg=true + :target: https://ci.appveyor.com/project/pytestbot/pytest-mock + +.. |coverage| image:: http://img.shields.io/coveralls/pytest-dev/pytest-mock.png + :target: https://coveralls.io/r/pytest-dev/pytest-mock + +.. |python| image:: https://img.shields.io/pypi/pyversions/pytest-mock.svg + :target: https://pypi.python.org/pypi/pytest-mock/ + +Usage +===== + +The ``mocker`` fixture has the same API as +`mock.patch `_, +supporting the same arguments: + +.. code-block:: python + + def test_foo(mocker): + # all valid calls + mocker.patch('os.remove') + mocker.patch.object(os, 'listdir', autospec=True) + mocked_isfile = mocker.patch('os.path.isfile') + +The supported methods are: + +* ``mocker.patch``: see http://www.voidspace.org.uk/python/mock/patch.html#patch. +* ``mocker.patch.object``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-object. +* ``mocker.patch.multiple``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-multiple. +* ``mocker.patch.dict``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-dict. +* ``mocker.stopall()``: stops all active patches up to this point. +* ``mocker.resetall()``: calls ``reset_mock()`` in all mocked objects up to this point. + +Some objects from the ``mock`` module are accessible directly from ``mocker`` for convenience: + +* `Mock `_ +* `MagicMock `_ +* `PropertyMock `_ +* `ANY `_ +* `call `_ *(Version 1.1)* +* `sentinel `_ *(Version 1.2)* +* `mock_open `_ + + +Spy +--- + +The spy acts exactly like the original method in all cases, except it allows use of `mock` +features with it, like retrieving call count. It also works for class and static methods. + + +.. code-block:: python + + def test_spy(mocker): + class Foo(object): + def bar(self): + return 42 + + foo = Foo() + mocker.spy(foo, 'bar') + assert foo.bar() == 42 + assert foo.bar.call_count == 1 + +Stub +---- + + +The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance. +May be passed a name to be used by the constructed stub object in its repr (useful for debugging). + +.. code-block:: python + + def test_stub(mocker): + def foo(on_something): + on_something('foo', 'bar') + + stub = mocker.stub(name='on_something_stub') + + foo(stub) + stub.assert_called_once_with('foo', 'bar') + + +Improved reporting of mock call assertion errors +------------------------------------------------ + + +This plugin monkeypatches the mock library to improve pytest output for failures +of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback +entries from the ``mock`` module. + +It also adds introspection information on differing call arguments when +calling the helper methods. This features catches `AssertionError` raised in +the method, and uses py.test's own `advanced assertions`_ to return a better +diff:: + + + m = mocker.patch.object(DS, 'create_char') + DS().create_char('Raistlin', class_='mag', gift=12) + > m.assert_called_once_with('Raistlin', class_='mage', gift=12) + E assert {'class_': 'mag', 'gift': 12} == {'class_': 'mage', 'gift': 12} + E Omitting 1 identical items, use -v to show + E Differing items: + E {'class_': 'mag'} != {'class_': 'mage'} + E Use -v to get the full diff + + +This is useful when asserting mock calls with many/nested arguments and trying +to quickly see the difference. + +This feature is probably safe, but if you encounter any problems it can be disabled in +your ``pytest.ini`` file: + +.. code-block:: ini + + [pytest] + mock_traceback_monkeypatch = false + +Note that this feature is automatically disabled with the ``--tb=native`` option. The underlying +mechanism used to suppress traceback entries from ``mock`` module does not work with that option +anyway plus it generates confusing messages on Python 3.5 due to exception chaining + +.. _advanced assertions: https://pytest.org/latest/assert.html + + +Requirements +============ + +* Python 2.6+, Python 3.3+ +* pytest +* mock (for Python 2) + + +Install +======= + +Install using `pip `_: + +.. code-block:: console + + $ pip install pytest-mock + +Changelog +========= + +Please consult the `changelog page`_. + +.. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst + +Why bother with a plugin? +========================= + +There are a number of different ``patch`` usages in the standard ``mock`` API, +but IMHO they don't scale very well when you have more than one or two +patches to apply. + +It may lead to an excessive nesting of ``with`` statements, breaking the flow +of the test: + +.. code-block:: python + + import mock + + def test_unix_fs(): + with mock.patch('os.remove'): + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + with mock.patch('os.listdir'): + assert UnixFS.ls('dir') == expected + # ... + + with mock.patch('shutil.copy'): + UnixFS.cp('src', 'dst') + # ... + + +One can use ``patch`` as a decorator to improve the flow of the test: + +.. code-block:: python + + @mock.patch('os.remove') + @mock.patch('os.listdir') + @mock.patch('shutil.copy') + def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove): + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + assert UnixFS.ls('dir') == expected + # ... + + UnixFS.cp('src', 'dst') + # ... + +But this poses a few disadvantages: + +- test functions must receive the mock objects as parameter, even if you don't plan to + access them directly; also, order depends on the order of the decorated ``patch`` + functions; +- receiving the mocks as parameters doesn't mix nicely with pytest's approach of + naming fixtures as parameters, or ``pytest.mark.parametrize``; +- you can't easily undo the mocking during the test execution; + + +**Note** + +Although mocker's API is intentionally the same as ``mock.patch``'s, its uses as context managers and function decorators are **not** supported. The purpose of this plugin is to make the use of context managers and function decorators for mocking unnecessary. Indeed, trying to use the functionality in ``mocker`` in this manner can lead to non-intuitive errors: + +.. code-block:: python + + def test_context_manager(mocker): + a = A() + with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): + assert a.doIt() == True + +.. code-block:: console + + ================================== FAILURES =================================== + ____________________________ test_context_manager _____________________________ + in test_context_manager + with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): + E AttributeError: __exit__ + + +License +======= + +Distributed under the terms of the `MIT`_ license. + +.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE diff --git a/_pytest_mock_version.py b/_pytest_mock_version.py new file mode 100644 index 0000000..021c6a0 --- /dev/null +++ b/_pytest_mock_version.py @@ -0,0 +1,4 @@ +# coding: utf-8 +# file generated by setuptools_scm +# don't change, don't track in version control +version = '1.3.0' diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..31aaa37 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,7 @@ +install: + - C:\Python35\python -m pip install tox + +build: false # Not a C# project + +test_script: + - C:\Python35\scripts\tox diff --git a/pytest_mock.egg-info/PKG-INFO b/pytest_mock.egg-info/PKG-INFO index 8733eaf..653efb9 100644 --- a/pytest_mock.egg-info/PKG-INFO +++ b/pytest_mock.egg-info/PKG-INFO @@ -1,282 +1,284 @@ -Metadata-Version: 1.1 -Name: pytest-mock -Version: 1.2 -Summary: Thin-wrapper around the mock package for easier use with py.test -Home-page: https://github.com/pytest-dev/pytest-mock/ -Author: Bruno Oliveira -Author-email: nicoddemus@gmail.com -License: MIT -Description: =========== - pytest-mock - =========== - - This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API - provided by the `mock package `_, - but with the benefit of not having to worry about undoing patches at the end - of a test: - - .. code-block:: python - - - def test_unix_fs(mocker): - mocker.patch('os.remove') - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - - .. Using PNG badges because PyPI doesn't support SVG - - |python| |version| |downloads| |ci| |appveyor| |coverage| - - .. |version| image:: http://img.shields.io/pypi/v/pytest-mock.png - :target: https://pypi.python.org/pypi/pytest-mock - - .. |downloads| image:: http://img.shields.io/pypi/dm/pytest-mock.png - :target: https://pypi.python.org/pypi/pytest-mock - - .. |ci| image:: http://img.shields.io/travis/pytest-dev/pytest-mock.png - :target: https://travis-ci.org/pytest-dev/pytest-mock - - .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/pid1t7iuwhkm9eh6/branch/master?svg=true - :target: https://ci.appveyor.com/project/pytestbot/pytest-mock - - .. |coverage| image:: http://img.shields.io/coveralls/pytest-dev/pytest-mock.png - :target: https://coveralls.io/r/pytest-dev/pytest-mock - - .. |python| image:: https://img.shields.io/pypi/pyversions/pytest-mock.svg - :target: https://pypi.python.org/pypi/pytest-mock/ - - Usage - ===== - - The ``mocker`` fixture has the same API as - `mock.patch `_, - supporting the same arguments: - - .. code-block:: python - - def test_foo(mocker): - # all valid calls - mocker.patch('os.remove') - mocker.patch.object(os, 'listdir', autospec=True) - mocked_isfile = mocker.patch('os.path.isfile') - - The supported methods are: - - * ``mocker.patch``: see http://www.voidspace.org.uk/python/mock/patch.html#patch. - * ``mocker.patch.object``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-object. - * ``mocker.patch.multiple``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-multiple. - * ``mocker.patch.dict``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-dict. - * ``mocker.stopall()``: stops all active patches up to this point. - * ``mocker.resetall()``: calls ``reset_mock()`` in all mocked objects up to this point. - - Some objects from the ``mock`` module are accessible directly from ``mocker`` for convenience: - - * `Mock `_ - * `MagicMock `_ - * `PropertyMock `_ - * `ANY `_ - * `call `_ *(Version 1.1)* - * `sentinel `_ *(Version 1.2)* - - - Spy - --- - - The spy acts exactly like the original method in all cases, except it allows use of `mock` - features with it, like retrieving call count. It also works for class and static methods. - - - .. code-block:: python - - def test_spy(mocker): - class Foo(object): - def bar(self): - return 42 - - foo = Foo() - mocker.spy(foo, 'bar') - assert foo.bar() == 42 - assert foo.bar.call_count == 1 - - Stub - ---- - - - The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance. - May be passed a name to be used by the constructed stub object in its repr (useful for debugging). - - .. code-block:: python - - def test_stub(mocker): - def foo(on_something): - on_something('foo', 'bar') - - stub = mocker.stub(name='on_something_stub') - - foo(stub) - stub.assert_called_once_with('foo', 'bar') - - - Improved reporting of mock call assertion errors - ------------------------------------------------ - - - This plugin monkeypatches the mock library to improve pytest output for failures - of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback - entries from the ``mock`` module. - - It also adds introspection information on differing call arguments when - calling the helper methods. This features catches `AssertionError` raised in - the method, and uses py.test's own `advanced assertions`_ to return a better - diff:: - - - m = mocker.patch.object(DS, 'create_char') - DS().create_char('Raistlin', class_='mag', gift=12) - > m.assert_called_once_with('Raistlin', class_='mage', gift=12) - E assert {'class_': 'mag', 'gift': 12} == {'class_': 'mage', 'gift': 12} - E Omitting 1 identical items, use -v to show - E Differing items: - E {'class_': 'mag'} != {'class_': 'mage'} - E Use -v to get the full diff - - - This is useful when asserting mock calls with many/nested arguments and trying - to quickly see the difference. - - This feature is probably safe, but if you encounter any problems it can be disabled in - your ``pytest.ini`` file: - - .. code-block:: ini - - [pytest] - mock_traceback_monkeypatch = false - - Note that this feature is automatically disabled with the ``--tb=native`` option. The underlying - mechanism used to suppress traceback entries from ``mock`` module does not work with that option - anyway plus it generates confusing messages on Python 3.5 due to exception chaining - - .. _advanced assertions: https://pytest.org/latest/assert.html - - - Requirements - ============ - - * Python 2.6+, Python 3.3+ - * pytest - * mock (for Python 2) - - - Install - ======= - - Install using `pip `_: - - .. code-block:: console - - $ pip install pytest-mock - - Changelog - ========= - - Please consult the `changelog page`_. - - .. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst - - Why bother with a plugin? - ========================= - - There are a number of different ``patch`` usages in the standard ``mock`` API, - but IMHO they don't scale very well when you have more than one or two - patches to apply. - - It may lead to an excessive nesting of ``with`` statements, breaking the flow - of the test: - - .. code-block:: python - - import mock - - def test_unix_fs(): - with mock.patch('os.remove'): - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - with mock.patch('os.listdir'): - assert UnixFS.ls('dir') == expected - # ... - - with mock.patch('shutil.copy'): - UnixFS.cp('src', 'dst') - # ... - - - One can use ``patch`` as a decorator to improve the flow of the test: - - .. code-block:: python - - @mock.patch('os.remove') - @mock.patch('os.listdir') - @mock.patch('shutil.copy') - def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove): - UnixFS.rm('file') - os.remove.assert_called_once_with('file') - - assert UnixFS.ls('dir') == expected - # ... - - UnixFS.cp('src', 'dst') - # ... - - But this poses a few disadvantages: - - - test functions must receive the mock objects as parameter, even if you don't plan to - access them directly; also, order depends on the order of the decorated ``patch`` - functions; - - receiving the mocks as parameters doesn't mix nicely with pytest's approach of - naming fixtures as parameters, or ``pytest.mark.parametrize``; - - you can't easily undo the mocking during the test execution; - - - **Note** - - Although mocker's API is intentionally the same as ``mock.patch``'s, its uses as context managers and function decorators are **not** supported. The purpose of this plugin is to make the use of context managers and function decorators for mocking unnecessary. Indeed, trying to use the functionality in ``mocker`` in this manner can lead to non-intuitive errors: - - .. code-block:: python - - def test_context_manager(mocker): - a = A() - with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): - assert a.doIt() == True - - .. code-block:: console - - ================================== FAILURES =================================== - ____________________________ test_context_manager _____________________________ - in test_context_manager - with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): - E AttributeError: __exit__ - - - License - ======= - - Distributed under the terms of the `MIT`_ license. - - .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE - -Keywords: pytest mock -Platform: any -Classifier: Development Status :: 5 - Production/Stable -Classifier: Framework :: Pytest -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 2.6 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Topic :: Software Development :: Testing +Metadata-Version: 1.1 +Name: pytest-mock +Version: 1.3.0 +Summary: Thin-wrapper around the mock package for easier use with py.test +Home-page: https://github.com/pytest-dev/pytest-mock/ +Author: Bruno Oliveira +Author-email: nicoddemus@gmail.com +License: MIT +Description: =========== + pytest-mock + =========== + + This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API + provided by the `mock package `_, + but with the benefit of not having to worry about undoing patches at the end + of a test: + + .. code-block:: python + + + def test_unix_fs(mocker): + mocker.patch('os.remove') + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + + .. Using PNG badges because PyPI doesn't support SVG + + |python| |version| |downloads| |ci| |appveyor| |coverage| + + .. |version| image:: http://img.shields.io/pypi/v/pytest-mock.png + :target: https://pypi.python.org/pypi/pytest-mock + + .. |downloads| image:: http://img.shields.io/pypi/dm/pytest-mock.png + :target: https://pypi.python.org/pypi/pytest-mock + + .. |ci| image:: http://img.shields.io/travis/pytest-dev/pytest-mock.png + :target: https://travis-ci.org/pytest-dev/pytest-mock + + .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/pid1t7iuwhkm9eh6/branch/master?svg=true + :target: https://ci.appveyor.com/project/pytestbot/pytest-mock + + .. |coverage| image:: http://img.shields.io/coveralls/pytest-dev/pytest-mock.png + :target: https://coveralls.io/r/pytest-dev/pytest-mock + + .. |python| image:: https://img.shields.io/pypi/pyversions/pytest-mock.svg + :target: https://pypi.python.org/pypi/pytest-mock/ + + Usage + ===== + + The ``mocker`` fixture has the same API as + `mock.patch `_, + supporting the same arguments: + + .. code-block:: python + + def test_foo(mocker): + # all valid calls + mocker.patch('os.remove') + mocker.patch.object(os, 'listdir', autospec=True) + mocked_isfile = mocker.patch('os.path.isfile') + + The supported methods are: + + * ``mocker.patch``: see http://www.voidspace.org.uk/python/mock/patch.html#patch. + * ``mocker.patch.object``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-object. + * ``mocker.patch.multiple``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-multiple. + * ``mocker.patch.dict``: see http://www.voidspace.org.uk/python/mock/patch.html#patch-dict. + * ``mocker.stopall()``: stops all active patches up to this point. + * ``mocker.resetall()``: calls ``reset_mock()`` in all mocked objects up to this point. + + Some objects from the ``mock`` module are accessible directly from ``mocker`` for convenience: + + * `Mock `_ + * `MagicMock `_ + * `PropertyMock `_ + * `ANY `_ + * `call `_ *(Version 1.1)* + * `sentinel `_ *(Version 1.2)* + * `mock_open `_ + + + Spy + --- + + The spy acts exactly like the original method in all cases, except it allows use of `mock` + features with it, like retrieving call count. It also works for class and static methods. + + + .. code-block:: python + + def test_spy(mocker): + class Foo(object): + def bar(self): + return 42 + + foo = Foo() + mocker.spy(foo, 'bar') + assert foo.bar() == 42 + assert foo.bar.call_count == 1 + + Stub + ---- + + + The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance. + May be passed a name to be used by the constructed stub object in its repr (useful for debugging). + + .. code-block:: python + + def test_stub(mocker): + def foo(on_something): + on_something('foo', 'bar') + + stub = mocker.stub(name='on_something_stub') + + foo(stub) + stub.assert_called_once_with('foo', 'bar') + + + Improved reporting of mock call assertion errors + ------------------------------------------------ + + + This plugin monkeypatches the mock library to improve pytest output for failures + of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback + entries from the ``mock`` module. + + It also adds introspection information on differing call arguments when + calling the helper methods. This features catches `AssertionError` raised in + the method, and uses py.test's own `advanced assertions`_ to return a better + diff:: + + + m = mocker.patch.object(DS, 'create_char') + DS().create_char('Raistlin', class_='mag', gift=12) + > m.assert_called_once_with('Raistlin', class_='mage', gift=12) + E assert {'class_': 'mag', 'gift': 12} == {'class_': 'mage', 'gift': 12} + E Omitting 1 identical items, use -v to show + E Differing items: + E {'class_': 'mag'} != {'class_': 'mage'} + E Use -v to get the full diff + + + This is useful when asserting mock calls with many/nested arguments and trying + to quickly see the difference. + + This feature is probably safe, but if you encounter any problems it can be disabled in + your ``pytest.ini`` file: + + .. code-block:: ini + + [pytest] + mock_traceback_monkeypatch = false + + Note that this feature is automatically disabled with the ``--tb=native`` option. The underlying + mechanism used to suppress traceback entries from ``mock`` module does not work with that option + anyway plus it generates confusing messages on Python 3.5 due to exception chaining + + .. _advanced assertions: https://pytest.org/latest/assert.html + + + Requirements + ============ + + * Python 2.6+, Python 3.3+ + * pytest + * mock (for Python 2) + + + Install + ======= + + Install using `pip `_: + + .. code-block:: console + + $ pip install pytest-mock + + Changelog + ========= + + Please consult the `changelog page`_. + + .. _changelog page: https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst + + Why bother with a plugin? + ========================= + + There are a number of different ``patch`` usages in the standard ``mock`` API, + but IMHO they don't scale very well when you have more than one or two + patches to apply. + + It may lead to an excessive nesting of ``with`` statements, breaking the flow + of the test: + + .. code-block:: python + + import mock + + def test_unix_fs(): + with mock.patch('os.remove'): + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + with mock.patch('os.listdir'): + assert UnixFS.ls('dir') == expected + # ... + + with mock.patch('shutil.copy'): + UnixFS.cp('src', 'dst') + # ... + + + One can use ``patch`` as a decorator to improve the flow of the test: + + .. code-block:: python + + @mock.patch('os.remove') + @mock.patch('os.listdir') + @mock.patch('shutil.copy') + def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove): + UnixFS.rm('file') + os.remove.assert_called_once_with('file') + + assert UnixFS.ls('dir') == expected + # ... + + UnixFS.cp('src', 'dst') + # ... + + But this poses a few disadvantages: + + - test functions must receive the mock objects as parameter, even if you don't plan to + access them directly; also, order depends on the order of the decorated ``patch`` + functions; + - receiving the mocks as parameters doesn't mix nicely with pytest's approach of + naming fixtures as parameters, or ``pytest.mark.parametrize``; + - you can't easily undo the mocking during the test execution; + + + **Note** + + Although mocker's API is intentionally the same as ``mock.patch``'s, its uses as context managers and function decorators are **not** supported. The purpose of this plugin is to make the use of context managers and function decorators for mocking unnecessary. Indeed, trying to use the functionality in ``mocker`` in this manner can lead to non-intuitive errors: + + .. code-block:: python + + def test_context_manager(mocker): + a = A() + with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): + assert a.doIt() == True + + .. code-block:: console + + ================================== FAILURES =================================== + ____________________________ test_context_manager _____________________________ + in test_context_manager + with mocker.patch.object(a, 'doIt', return_value=True, autospec=True): + E AttributeError: __exit__ + + + License + ======= + + Distributed under the terms of the `MIT`_ license. + + .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE + +Keywords: pytest mock +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Framework :: Pytest +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Topic :: Software Development :: Testing diff --git a/pytest_mock.egg-info/SOURCES.txt b/pytest_mock.egg-info/SOURCES.txt index 0a429db..60fecfe 100644 --- a/pytest_mock.egg-info/SOURCES.txt +++ b/pytest_mock.egg-info/SOURCES.txt @@ -1,10 +1,16 @@ +.gitignore +.travis.yml +CHANGELOG.rst LICENSE MANIFEST.in README.rst +_pytest_mock_version.py +appveyor.yml pytest_mock.py setup.cfg setup.py test_pytest_mock.py +tox.ini pytest_mock.egg-info/PKG-INFO pytest_mock.egg-info/SOURCES.txt pytest_mock.egg-info/dependency_links.txt diff --git a/pytest_mock.egg-info/top_level.txt b/pytest_mock.egg-info/top_level.txt index 59bf78d..838ba8c 100644 --- a/pytest_mock.egg-info/top_level.txt +++ b/pytest_mock.egg-info/top_level.txt @@ -1 +1,2 @@ +_pytest_mock_version pytest_mock diff --git a/pytest_mock.py b/pytest_mock.py index bbb1973..7fc3afe 100644 --- a/pytest_mock.py +++ b/pytest_mock.py @@ -1,258 +1,264 @@ -import inspect - -import pytest - -try: - import mock as mock_module -except ImportError: - import unittest.mock as mock_module - -version = '1.2' - - -class MockFixture(object): - """ - Fixture that provides the same interface to functions in the mock module, - ensuring that they are uninstalled at the end of each test. - """ - - Mock = mock_module.Mock - MagicMock = mock_module.MagicMock - PropertyMock = mock_module.PropertyMock - call = mock_module.call - ANY = mock_module.ANY - sentinel = mock_module.sentinel - - def __init__(self): - self._patches = [] # list of mock._patch objects - self._mocks = [] # list of MagicMock objects - self.patch = self._Patcher(self._patches, self._mocks) - - def resetall(self): - """ - Call reset_mock() on all patchers started by this fixture. - """ - for m in self._mocks: - m.reset_mock() - - def stopall(self): - """ - Stop all patchers started by this fixture. Can be safely called multiple - times. - """ - for p in reversed(self._patches): - p.stop() - self._patches[:] = [] - self._mocks[:] = [] - - def spy(self, obj, name): - """ - Creates a spy of method. It will run method normally, but it is now - possible to use `mock` call features with it, like call count. - - :param object obj: An object. - :param unicode name: A method in object. - :rtype: mock.MagicMock - :return: Spy object. - """ - method = getattr(obj, name) - - autospec = inspect.ismethod(method) or inspect.isfunction(method) - # Can't use autospec classmethod or staticmethod objects - # see: https://bugs.python.org/issue23078 - if inspect.isclass(obj): - # Bypass class descriptor: - # http://stackoverflow.com/questions/14187973/python3-check-if-method-is-static - try: - value = obj.__getattribute__(obj, name) - except AttributeError: - pass - else: - if isinstance(value, (classmethod, staticmethod)): - autospec = False - - result = self.patch.object(obj, name, side_effect=method, - autospec=autospec) - return result - - def stub(self, name=None): - """ - Creates a stub method. It accepts any arguments. Ideal to register to - callbacks in tests. - - :param name: the constructed stub's name as used in repr - :rtype: mock.MagicMock - :return: Stub object. - """ - return mock_module.MagicMock(spec=lambda *args, **kwargs: None, name=name) - - class _Patcher(object): - """ - Object to provide the same interface as mock.patch, mock.patch.object, - etc. We need this indirection to keep the same API of the mock package. - """ - - def __init__(self, patches, mocks): - self._patches = patches - self._mocks = mocks - - def _start_patch(self, mock_func, *args, **kwargs): - """Patches something by calling the given function from the mock - module, registering the patch to stop it later and returns the - mock object resulting from the mock call. - """ - p = mock_func(*args, **kwargs) - mocked = p.start() - self._patches.append(p) - self._mocks.append(mocked) - return mocked - - def object(self, *args, **kwargs): - """API to mock.patch.object""" - return self._start_patch(mock_module.patch.object, *args, **kwargs) - - def multiple(self, *args, **kwargs): - """API to mock.patch.multiple""" - return self._start_patch(mock_module.patch.multiple, *args, - **kwargs) - - def dict(self, *args, **kwargs): - """API to mock.patch.dict""" - return self._start_patch(mock_module.patch.dict, *args, **kwargs) - - def __call__(self, *args, **kwargs): - """API to mock.patch""" - return self._start_patch(mock_module.patch, *args, **kwargs) - - -@pytest.yield_fixture -def mocker(): - """ - return an object that has the same interface to the `mock` module, but - takes care of automatically undoing all patches after each test method. - """ - result = MockFixture() - yield result - result.stopall() - - -@pytest.fixture -def mock(mocker): - """ - Same as "mocker", but kept only for backward compatibility. - """ - import warnings - warnings.warn('"mock" fixture has been deprecated, use "mocker" instead', - DeprecationWarning) - return mocker - - -_mock_module_patches = [] -_mock_module_originals = {} - - -def assert_wrapper(__wrapped_mock_method__, *args, **kwargs): - __tracebackhide__ = True - try: - __wrapped_mock_method__(*args, **kwargs) - except AssertionError as e: - __mock_self = args[0] - if __mock_self.call_args is not None: - actual_args, actual_kwargs = __mock_self.call_args - assert actual_args == args[1:] - assert actual_kwargs == kwargs - raise AssertionError(*e.args) - - -def wrap_assert_not_called(*args, **kwargs): - __tracebackhide__ = True - assert_wrapper(_mock_module_originals["assert_not_called"], - *args, **kwargs) - - -def wrap_assert_called_with(*args, **kwargs): - __tracebackhide__ = True - assert_wrapper(_mock_module_originals["assert_called_with"], - *args, **kwargs) - - -def wrap_assert_called_once_with(*args, **kwargs): - __tracebackhide__ = True - assert_wrapper(_mock_module_originals["assert_called_once_with"], - *args, **kwargs) - - -def wrap_assert_has_calls(*args, **kwargs): - __tracebackhide__ = True - assert_wrapper(_mock_module_originals["assert_has_calls"], - *args, **kwargs) - - -def wrap_assert_any_call(*args, **kwargs): - __tracebackhide__ = True - assert_wrapper(_mock_module_originals["assert_any_call"], - *args, **kwargs) - - -def wrap_assert_methods(config): - """ - Wrap assert methods of mock module so we can hide their traceback and - add introspection information to specified argument asserts. - """ - # Make sure we only do this once - if _mock_module_originals: - return - - wrappers = { - 'assert_not_called': wrap_assert_not_called, - 'assert_called_with': wrap_assert_called_with, - 'assert_called_once_with': wrap_assert_called_once_with, - 'assert_has_calls': wrap_assert_has_calls, - 'assert_any_call': wrap_assert_any_call, - } - for method, wrapper in wrappers.items(): - try: - original = getattr(mock_module.NonCallableMock, method) - except AttributeError: - continue - _mock_module_originals[method] = original - patcher = mock_module.patch.object( - mock_module.NonCallableMock, method, wrapper) - patcher.start() - _mock_module_patches.append(patcher) - - if hasattr(config, 'add_cleanup'): - add_cleanup = config.add_cleanup - else: - # pytest 2.7 compatibility - add_cleanup = config._cleanup.append - add_cleanup(unwrap_assert_methods) - - -def unwrap_assert_methods(): - for patcher in _mock_module_patches: - patcher.stop() - _mock_module_patches[:] = [] - _mock_module_originals.clear() - - -def pytest_addoption(parser): - parser.addini('mock_traceback_monkeypatch', - 'Monkeypatch the mock library to improve reporting of the ' - 'assert_called_... methods', - default=True) - - -def parse_ini_boolean(value): - if value in (True, False): - return value - try: - return {'true': True, 'false': False}[value.lower()] - except KeyError: - raise ValueError('unknown string for bool: %r' % value) - - -def pytest_configure(config): - tb = config.getoption('--tb') - if parse_ini_boolean(config.getini('mock_traceback_monkeypatch')) and tb != 'native': - wrap_assert_methods(config) +import inspect + +import pytest + +try: + import mock as mock_module +except ImportError: + import unittest.mock as mock_module + +from _pytest_mock_version import version +__version__ = version + + +class MockFixture(object): + """ + Fixture that provides the same interface to functions in the mock module, + ensuring that they are uninstalled at the end of each test. + """ + + Mock = mock_module.Mock + MagicMock = mock_module.MagicMock + PropertyMock = mock_module.PropertyMock + call = mock_module.call + ANY = mock_module.ANY + sentinel = mock_module.sentinel + mock_open = mock_module.mock_open + + def __init__(self): + self._patches = [] # list of mock._patch objects + self._mocks = [] # list of MagicMock objects + self.patch = self._Patcher(self._patches, self._mocks) + # temporary fix: this should be at class level, but is blowing + # up in Python 3.6 + self.sentinel = mock_module.sentinel + self.mock_open = mock_module.mock_open + + def resetall(self): + """ + Call reset_mock() on all patchers started by this fixture. + """ + for m in self._mocks: + m.reset_mock() + + def stopall(self): + """ + Stop all patchers started by this fixture. Can be safely called multiple + times. + """ + for p in reversed(self._patches): + p.stop() + self._patches[:] = [] + self._mocks[:] = [] + + def spy(self, obj, name): + """ + Creates a spy of method. It will run method normally, but it is now + possible to use `mock` call features with it, like call count. + + :param object obj: An object. + :param unicode name: A method in object. + :rtype: mock.MagicMock + :return: Spy object. + """ + method = getattr(obj, name) + + autospec = inspect.ismethod(method) or inspect.isfunction(method) + # Can't use autospec classmethod or staticmethod objects + # see: https://bugs.python.org/issue23078 + if inspect.isclass(obj): + # Bypass class descriptor: + # http://stackoverflow.com/questions/14187973/python3-check-if-method-is-static + try: + value = obj.__getattribute__(obj, name) + except AttributeError: + pass + else: + if isinstance(value, (classmethod, staticmethod)): + autospec = False + + result = self.patch.object(obj, name, side_effect=method, + autospec=autospec) + return result + + def stub(self, name=None): + """ + Creates a stub method. It accepts any arguments. Ideal to register to + callbacks in tests. + + :param name: the constructed stub's name as used in repr + :rtype: mock.MagicMock + :return: Stub object. + """ + return mock_module.MagicMock(spec=lambda *args, **kwargs: None, name=name) + + class _Patcher(object): + """ + Object to provide the same interface as mock.patch, mock.patch.object, + etc. We need this indirection to keep the same API of the mock package. + """ + + def __init__(self, patches, mocks): + self._patches = patches + self._mocks = mocks + + def _start_patch(self, mock_func, *args, **kwargs): + """Patches something by calling the given function from the mock + module, registering the patch to stop it later and returns the + mock object resulting from the mock call. + """ + p = mock_func(*args, **kwargs) + mocked = p.start() + self._patches.append(p) + self._mocks.append(mocked) + return mocked + + def object(self, *args, **kwargs): + """API to mock.patch.object""" + return self._start_patch(mock_module.patch.object, *args, **kwargs) + + def multiple(self, *args, **kwargs): + """API to mock.patch.multiple""" + return self._start_patch(mock_module.patch.multiple, *args, + **kwargs) + + def dict(self, *args, **kwargs): + """API to mock.patch.dict""" + return self._start_patch(mock_module.patch.dict, *args, **kwargs) + + def __call__(self, *args, **kwargs): + """API to mock.patch""" + return self._start_patch(mock_module.patch, *args, **kwargs) + + +@pytest.yield_fixture +def mocker(): + """ + return an object that has the same interface to the `mock` module, but + takes care of automatically undoing all patches after each test method. + """ + result = MockFixture() + yield result + result.stopall() + + +@pytest.fixture +def mock(mocker): + """ + Same as "mocker", but kept only for backward compatibility. + """ + import warnings + warnings.warn('"mock" fixture has been deprecated, use "mocker" instead', + DeprecationWarning) + return mocker + + +_mock_module_patches = [] +_mock_module_originals = {} + + +def assert_wrapper(__wrapped_mock_method__, *args, **kwargs): + __tracebackhide__ = True + try: + __wrapped_mock_method__(*args, **kwargs) + except AssertionError as e: + __mock_self = args[0] + if __mock_self.call_args is not None: + actual_args, actual_kwargs = __mock_self.call_args + assert actual_args == args[1:] + assert actual_kwargs == kwargs + raise AssertionError(*e.args) + + +def wrap_assert_not_called(*args, **kwargs): + __tracebackhide__ = True + assert_wrapper(_mock_module_originals["assert_not_called"], + *args, **kwargs) + + +def wrap_assert_called_with(*args, **kwargs): + __tracebackhide__ = True + assert_wrapper(_mock_module_originals["assert_called_with"], + *args, **kwargs) + + +def wrap_assert_called_once_with(*args, **kwargs): + __tracebackhide__ = True + assert_wrapper(_mock_module_originals["assert_called_once_with"], + *args, **kwargs) + + +def wrap_assert_has_calls(*args, **kwargs): + __tracebackhide__ = True + assert_wrapper(_mock_module_originals["assert_has_calls"], + *args, **kwargs) + + +def wrap_assert_any_call(*args, **kwargs): + __tracebackhide__ = True + assert_wrapper(_mock_module_originals["assert_any_call"], + *args, **kwargs) + + +def wrap_assert_methods(config): + """ + Wrap assert methods of mock module so we can hide their traceback and + add introspection information to specified argument asserts. + """ + # Make sure we only do this once + if _mock_module_originals: + return + + wrappers = { + 'assert_not_called': wrap_assert_not_called, + 'assert_called_with': wrap_assert_called_with, + 'assert_called_once_with': wrap_assert_called_once_with, + 'assert_has_calls': wrap_assert_has_calls, + 'assert_any_call': wrap_assert_any_call, + } + for method, wrapper in wrappers.items(): + try: + original = getattr(mock_module.NonCallableMock, method) + except AttributeError: # pragma: no cover + continue + _mock_module_originals[method] = original + patcher = mock_module.patch.object( + mock_module.NonCallableMock, method, wrapper) + patcher.start() + _mock_module_patches.append(patcher) + + if hasattr(config, 'add_cleanup'): + add_cleanup = config.add_cleanup + else: + # pytest 2.7 compatibility + add_cleanup = config._cleanup.append + add_cleanup(unwrap_assert_methods) + + +def unwrap_assert_methods(): + for patcher in _mock_module_patches: + patcher.stop() + _mock_module_patches[:] = [] + _mock_module_originals.clear() + + +def pytest_addoption(parser): + parser.addini('mock_traceback_monkeypatch', + 'Monkeypatch the mock library to improve reporting of the ' + 'assert_called_... methods', + default=True) + + +def parse_ini_boolean(value): + if value in (True, False): + return value + try: + return {'true': True, 'false': False}[value.lower()] + except KeyError: + raise ValueError('unknown string for bool: %r' % value) + + +def pytest_configure(config): + tb = config.getoption('--tb') + if parse_ini_boolean(config.getini('mock_traceback_monkeypatch')) and tb != 'native': + wrap_assert_methods(config) diff --git a/setup.cfg b/setup.cfg index 9ea0a41..6f08d0e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,8 +1,8 @@ -[bdist_wheel] -universal = 1 - -[egg_info] -tag_build = -tag_date = 0 -tag_svn_revision = 0 - +[bdist_wheel] +universal = 1 + +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + diff --git a/setup.py b/setup.py index 6a8e126..16c8319 100644 --- a/setup.py +++ b/setup.py @@ -1,45 +1,43 @@ -import re - -from setuptools import setup - -with open('pytest_mock.py') as f: - m = re.search("version = '(.*)'", f.read()) - assert m is not None - version = m.group(1) - -setup( - name='pytest-mock', - version=version, - entry_points={ - 'pytest11': ['pytest_mock = pytest_mock'], - }, - py_modules=['pytest_mock'], - platforms='any', - install_requires=[ - 'pytest>=2.7', - ], - extras_require={ - ':python_version=="2.6" or python_version=="2.7"': ['mock'], - }, - url='https://github.com/pytest-dev/pytest-mock/', - license='MIT', - author='Bruno Oliveira', - author_email='nicoddemus@gmail.com', - description='Thin-wrapper around the mock package for easier use with py.test', - long_description=open('README.rst').read(), - keywords="pytest mock", - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Framework :: Pytest', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Topic :: Software Development :: Testing', - ] -) +import re + +from setuptools import setup + + +setup( + name='pytest-mock', + entry_points={ + 'pytest11': ['pytest_mock = pytest_mock'], + }, + py_modules=['pytest_mock', '_pytest_mock_version'], + platforms='any', + install_requires=[ + 'pytest>=2.7', + ], + extras_require={ + ':python_version=="2.6" or python_version=="2.7"': ['mock'], + }, + use_scm_version={'write_to': '_pytest_mock_version.py'}, + setup_requires=['setuptools_scm'], + url='https://github.com/pytest-dev/pytest-mock/', + license='MIT', + author='Bruno Oliveira', + author_email='nicoddemus@gmail.com', + description='Thin-wrapper around the mock package for easier use with py.test', + long_description=open('README.rst').read(), + keywords="pytest mock", + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Framework :: Pytest', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Topic :: Software Development :: Testing', + ] +) diff --git a/test_pytest_mock.py b/test_pytest_mock.py index 1d55295..ad439d6 100644 --- a/test_pytest_mock.py +++ b/test_pytest_mock.py @@ -1,500 +1,500 @@ -import os -import platform -import sys -from contextlib import contextmanager - -import py.code - -import pytest - -pytest_plugins = 'pytester' - -# could not make some of the tests work on PyPy, patches are welcome! -skip_pypy = pytest.mark.skipif(platform.python_implementation() == 'PyPy', - reason='could not make work on pypy') - - -class UnixFS(object): - """ - Wrapper to os functions to simulate a Unix file system, used for testing - the mock fixture. - """ - - @classmethod - def rm(cls, filename): - os.remove(filename) - - @classmethod - def ls(cls, path): - return os.listdir(path) - - -@pytest.fixture -def check_unix_fs_mocked(tmpdir, mocker): - """ - performs a standard test in a UnixFS, assuming that both `os.remove` and - `os.listdir` have been mocked previously. - """ - - def check(mocked_rm, mocked_ls): - assert mocked_rm is os.remove - assert mocked_ls is os.listdir - - file_name = tmpdir / 'foo.txt' - file_name.ensure() - - UnixFS.rm(str(file_name)) - mocked_rm.assert_called_once_with(str(file_name)) - assert os.path.isfile(str(file_name)) - - mocked_ls.return_value = ['bar.txt'] - assert UnixFS.ls(str(tmpdir)) == ['bar.txt'] - mocked_ls.assert_called_once_with(str(tmpdir)) - - mocker.stopall() - - assert UnixFS.ls(str(tmpdir)) == ['foo.txt'] - UnixFS.rm(str(file_name)) - assert not os.path.isfile(str(file_name)) - - return check - - -def mock_using_patch_object(mocker): - return mocker.patch.object(os, 'remove'), mocker.patch.object(os, 'listdir') - - -def mock_using_patch(mocker): - return mocker.patch('os.remove'), mocker.patch('os.listdir') - - -def mock_using_patch_multiple(mocker): - from pytest_mock import mock_module - - r = mocker.patch.multiple('os', remove=mock_module.DEFAULT, - listdir=mock_module.DEFAULT) - return r['remove'], r['listdir'] - - -@pytest.mark.parametrize('mock_fs', [mock_using_patch_object, mock_using_patch, - mock_using_patch_multiple], -) -def test_mock_patches(mock_fs, mocker, check_unix_fs_mocked): - """ - Installs mocks into `os` functions and performs a standard testing of - mock functionality. We parametrize different mock methods to ensure - all (intended, at least) mock API is covered. - """ - # mock it twice on purpose to ensure we unmock it correctly later - mock_fs(mocker) - mocked_rm, mocked_ls = mock_fs(mocker) - check_unix_fs_mocked(mocked_rm, mocked_ls) - - -def test_mock_patch_dict(mocker): - """ - Testing - :param mock: - """ - x = {'original': 1} - mocker.patch.dict(x, values=[('new', 10)], clear=True) - assert x == {'new': 10} - mocker.stopall() - assert x == {'original': 1} - - -def test_mock_fixture_is_deprecated(testdir): - """ - Test that a warning emitted when using deprecated "mock" fixture. - """ - testdir.makepyfile(''' - import warnings - import os - warnings.simplefilter('always') - - def test_foo(mock, tmpdir): - mock.patch('os.listdir', return_value=['mocked']) - assert os.listdir(str(tmpdir)) == ['mocked'] - mock.stopall() - assert os.listdir(str(tmpdir)) == [] - ''') - result = testdir.runpytest('-s') - result.stderr.fnmatch_lines(['*"mock" fixture has been deprecated*']) - - -def test_deprecated_mock(mock, tmpdir): - """ - Use backward-compatibility-only mock fixture to ensure complete coverage. - """ - mock.patch('os.listdir', return_value=['mocked']) - assert os.listdir(str(tmpdir)) == ['mocked'] - mock.stopall() - assert os.listdir(str(tmpdir)) == [] - - -@pytest.mark.parametrize('name', ['MagicMock', 'PropertyMock', 'Mock', 'call', 'ANY', 'sentinel']) -def test_mocker_aliases(name): - from pytest_mock import mock_module, MockFixture - - mocker = MockFixture() - assert getattr(mocker, name) is getattr(mock_module, name) - - -def test_mocker_resetall(mocker): - listdir = mocker.patch('os.listdir') - open = mocker.patch('os.open') - - listdir("/tmp") - open("/tmp/foo.txt") - listdir.assert_called_once_with("/tmp") - open.assert_called_once_with("/tmp/foo.txt") - - mocker.resetall() - - assert not listdir.called - assert not open.called - - -class TestMockerStub: - def test_call(self, mocker): - stub = mocker.stub() - stub('foo', 'bar') - stub.assert_called_once_with('foo', 'bar') - - def test_repr_with_no_name(self, mocker): - stub = mocker.stub() - assert not 'name' in repr(stub) - - def test_repr_with_name(self, mocker): - test_name = 'funny walk' - stub = mocker.stub(name=test_name) - assert "name={0!r}".format(test_name) in repr(stub) - - def __test_failure_message(self, mocker, **kwargs): - expected_name = kwargs.get('name') or 'mock' - expected_message = 'Expected call: {0}()\nNot called'.format(expected_name) - stub = mocker.stub(**kwargs) - with pytest.raises(AssertionError) as exc_info: - stub.assert_called_with() - assert exc_info.value.msg == expected_message - - def test_failure_message_with_no_name(self, mocker): - self.__test_failure_message(mocker) - - @pytest.mark.parametrize('name', (None, '', 'f', 'The Castle of aaarrrrggh')) - def test_failure_message_with_name(self, mocker, name): - self.__test_failure_message(mocker, name=name) - - -def test_instance_method_spy(mocker): - class Foo(object): - - def bar(self, arg): - return arg * 2 - - foo = Foo() - other = Foo() - spy = mocker.spy(foo, 'bar') - assert foo.bar(arg=10) == 20 - assert other.bar(arg=10) == 20 - foo.bar.assert_called_once_with(arg=10) - spy.assert_called_once_with(arg=10) - - -@skip_pypy -def test_instance_method_by_class_spy(mocker): - from pytest_mock import mock_module - - class Foo(object): - - def bar(self, arg): - return arg * 2 - - spy = mocker.spy(Foo, 'bar') - foo = Foo() - other = Foo() - assert foo.bar(arg=10) == 20 - assert other.bar(arg=10) == 20 - calls = [mock_module.call(foo, arg=10), mock_module.call(other, arg=10)] - assert spy.call_args_list == calls - - -@skip_pypy -def test_instance_method_by_subclass_spy(mocker): - from pytest_mock import mock_module - - class Base(object): - - def bar(self, arg): - return arg * 2 - - class Foo(Base): - pass - - spy = mocker.spy(Foo, 'bar') - foo = Foo() - other = Foo() - assert foo.bar(arg=10) == 20 - assert other.bar(arg=10) == 20 - calls = [mock_module.call(foo, arg=10), mock_module.call(other, arg=10)] - assert spy.call_args_list == calls - - -@skip_pypy -def test_class_method_spy(mocker): - class Foo(object): - - @classmethod - def bar(cls, arg): - return arg * 2 - - spy = mocker.spy(Foo, 'bar') - assert Foo.bar(arg=10) == 20 - Foo.bar.assert_called_once_with(arg=10) - spy.assert_called_once_with(arg=10) - - -@skip_pypy -@pytest.mark.xfail(sys.version_info[0] == 2, reason='does not work on Python 2') -def test_class_method_subclass_spy(mocker): - class Base(object): - - @classmethod - def bar(self, arg): - return arg * 2 - - class Foo(Base): - pass - - spy = mocker.spy(Foo, 'bar') - assert Foo.bar(arg=10) == 20 - Foo.bar.assert_called_once_with(arg=10) - spy.assert_called_once_with(arg=10) - - -@skip_pypy -def test_class_method_with_metaclass_spy(mocker): - class MetaFoo(type): - pass - - class Foo(object): - - __metaclass__ = MetaFoo - - @classmethod - def bar(cls, arg): - return arg * 2 - - spy = mocker.spy(Foo, 'bar') - assert Foo.bar(arg=10) == 20 - Foo.bar.assert_called_once_with(arg=10) - spy.assert_called_once_with(arg=10) - - -@skip_pypy -def test_static_method_spy(mocker): - class Foo(object): - - @staticmethod - def bar(arg): - return arg * 2 - - spy = mocker.spy(Foo, 'bar') - assert Foo.bar(arg=10) == 20 - Foo.bar.assert_called_once_with(arg=10) - spy.assert_called_once_with(arg=10) - - -@skip_pypy -@pytest.mark.xfail(sys.version_info[0] == 2, reason='does not work on Python 2') -def test_static_method_subclass_spy(mocker): - class Base(object): - - @staticmethod - def bar(arg): - return arg * 2 - - class Foo(Base): - pass - - spy = mocker.spy(Foo, 'bar') - assert Foo.bar(arg=10) == 20 - Foo.bar.assert_called_once_with(arg=10) - spy.assert_called_once_with(arg=10) - - -@contextmanager -def assert_traceback(): - """ - Assert that this file is at the top of the filtered traceback - """ - try: - yield - except AssertionError: - traceback = py.code.ExceptionInfo().traceback - crashentry = traceback.getcrashentry() - assert crashentry.path == __file__ - else: - raise AssertionError("DID NOT RAISE") - - -@contextmanager -def assert_argument_introspection(left, right): - """ - Assert detailed argument introspection is used - """ - try: - yield - except AssertionError as e: - # this may be a bit too assuming, but seems nicer then hard-coding - import _pytest.assertion.util as util - # NOTE: we assert with either verbose or not, depending on how our own - # test was run by examining sys.argv - verbose = any(a.startswith('-v') for a in sys.argv) - expected = '\n '.join(util._compare_eq_iterable(left, right, verbose)) - assert expected in e.msg - else: - raise AssertionError("DID NOT RAISE") - - -@pytest.mark.skipif(sys.version_info[:2] in [(3, 3), (3, 4)], - reason="assert_not_called not available in python 3.3 and 3.4") -def test_assert_not_called_wrapper(mocker): - stub = mocker.stub() - stub.assert_not_called() - stub() - with assert_traceback(): - stub.assert_not_called() - - -def test_assert_called_with_wrapper(mocker): - stub = mocker.stub() - stub("foo") - stub.assert_called_with("foo") - with assert_traceback(): - stub.assert_called_with("bar") - - -def test_assert_called_once_with_wrapper(mocker): - stub = mocker.stub() - stub("foo") - stub.assert_called_once_with("foo") - stub("foo") - with assert_traceback(): - stub.assert_called_once_with("foo") - - -def test_assert_called_args_with_introspection(mocker): - stub = mocker.stub() - - complex_args = ('a', 1, set(['test'])) - wrong_args = ('b', 2, set(['jest'])) - - stub(*complex_args) - stub.assert_called_with(*complex_args) - stub.assert_called_once_with(*complex_args) - - with assert_argument_introspection(complex_args, wrong_args): - stub.assert_called_with(*wrong_args) - stub.assert_called_once_with(*wrong_args) - - -def test_assert_called_kwargs_with_introspection(mocker): - stub = mocker.stub() - - complex_kwargs = dict(foo={'bar': 1, 'baz': 'spam'}) - wrong_kwargs = dict(foo={'goo': 1, 'baz': 'bran'}) - - stub(**complex_kwargs) - stub.assert_called_with(**complex_kwargs) - stub.assert_called_once_with(**complex_kwargs) - - with assert_argument_introspection(complex_kwargs, wrong_kwargs): - stub.assert_called_with(**wrong_kwargs) - stub.assert_called_once_with(**wrong_kwargs) - - -def test_assert_any_call_wrapper(mocker): - stub = mocker.stub() - stub("foo") - stub("foo") - stub.assert_any_call("foo") - with assert_traceback(): - stub.assert_any_call("bar") - - -def test_assert_has_calls(mocker): - from pytest_mock import mock_module - stub = mocker.stub() - stub("foo") - stub.assert_has_calls([mock_module.call("foo")]) - with assert_traceback(): - stub.assert_has_calls([mock_module.call("bar")]) - - -def test_monkeypatch_ini(mocker, testdir): - # Make sure the following function actually tests something - stub = mocker.stub() - assert stub.assert_called_with.__module__ != stub.__module__ - - testdir.makepyfile(""" - import py.code - def test_foo(mocker): - stub = mocker.stub() - assert stub.assert_called_with.__module__ == stub.__module__ - """) - testdir.makeini(""" - [pytest] - mock_traceback_monkeypatch = false - """) - if hasattr(testdir, 'runpytest_subprocess'): - result = testdir.runpytest_subprocess() - else: - # pytest 2.7.X - result = testdir.runpytest() - assert result.ret == 0 - - -def test_parse_ini_boolean(): - import pytest_mock - assert pytest_mock.parse_ini_boolean('True') is True - assert pytest_mock.parse_ini_boolean('false') is False - with pytest.raises(ValueError): - pytest_mock.parse_ini_boolean('foo') - - -def test_patched_method_parameter_name(mocker): - """Test that our internal code uses uncommon names when wrapping other - "mock" methods to avoid conflicts with user code (#31). - """ - - class Request: - @classmethod - def request(cls, method, args): - pass - - m = mocker.patch.object(Request, 'request') - Request.request(method='get', args={'type': 'application/json'}) - m.assert_called_once_with(method='get', args={'type': 'application/json'}) - - -def test_monkeypatch_native(testdir): - """Automatically disable monkeypatching when --tb=native. - """ - testdir.makepyfile(""" - def test_foo(mocker): - stub = mocker.stub() - stub(1, greet='hello') - stub.assert_called_once_with(1, greet='hey') - """) - if hasattr(testdir, 'runpytest_subprocess'): - result = testdir.runpytest_subprocess('--tb=native') - else: - # pytest 2.7.X - result = testdir.runpytest('--tb=native') - assert result.ret == 1 - assert 'During handling of the above exception' not in result.stdout.str() - assert 'Differing items:' not in result.stdout.str() - traceback_lines = [x for x in result.stdout.str().splitlines() - if 'Traceback (most recent call last)' in x] - assert len(traceback_lines) == 1 # make sure there are no duplicated tracebacks (#44) +import os +import platform +import sys +from contextlib import contextmanager + +import py.code + +import pytest + +pytest_plugins = 'pytester' + +# could not make some of the tests work on PyPy, patches are welcome! +skip_pypy = pytest.mark.skipif(platform.python_implementation() == 'PyPy', + reason='could not make work on pypy') + + +class UnixFS(object): + """ + Wrapper to os functions to simulate a Unix file system, used for testing + the mock fixture. + """ + + @classmethod + def rm(cls, filename): + os.remove(filename) + + @classmethod + def ls(cls, path): + return os.listdir(path) + + +@pytest.fixture +def check_unix_fs_mocked(tmpdir, mocker): + """ + performs a standard test in a UnixFS, assuming that both `os.remove` and + `os.listdir` have been mocked previously. + """ + + def check(mocked_rm, mocked_ls): + assert mocked_rm is os.remove + assert mocked_ls is os.listdir + + file_name = tmpdir / 'foo.txt' + file_name.ensure() + + UnixFS.rm(str(file_name)) + mocked_rm.assert_called_once_with(str(file_name)) + assert os.path.isfile(str(file_name)) + + mocked_ls.return_value = ['bar.txt'] + assert UnixFS.ls(str(tmpdir)) == ['bar.txt'] + mocked_ls.assert_called_once_with(str(tmpdir)) + + mocker.stopall() + + assert UnixFS.ls(str(tmpdir)) == ['foo.txt'] + UnixFS.rm(str(file_name)) + assert not os.path.isfile(str(file_name)) + + return check + + +def mock_using_patch_object(mocker): + return mocker.patch.object(os, 'remove'), mocker.patch.object(os, 'listdir') + + +def mock_using_patch(mocker): + return mocker.patch('os.remove'), mocker.patch('os.listdir') + + +def mock_using_patch_multiple(mocker): + from pytest_mock import mock_module + + r = mocker.patch.multiple('os', remove=mock_module.DEFAULT, + listdir=mock_module.DEFAULT) + return r['remove'], r['listdir'] + + +@pytest.mark.parametrize('mock_fs', [mock_using_patch_object, mock_using_patch, + mock_using_patch_multiple], +) +def test_mock_patches(mock_fs, mocker, check_unix_fs_mocked): + """ + Installs mocks into `os` functions and performs a standard testing of + mock functionality. We parametrize different mock methods to ensure + all (intended, at least) mock API is covered. + """ + # mock it twice on purpose to ensure we unmock it correctly later + mock_fs(mocker) + mocked_rm, mocked_ls = mock_fs(mocker) + check_unix_fs_mocked(mocked_rm, mocked_ls) + + +def test_mock_patch_dict(mocker): + """ + Testing + :param mock: + """ + x = {'original': 1} + mocker.patch.dict(x, values=[('new', 10)], clear=True) + assert x == {'new': 10} + mocker.stopall() + assert x == {'original': 1} + + +def test_mock_fixture_is_deprecated(testdir): + """ + Test that a warning emitted when using deprecated "mock" fixture. + """ + testdir.makepyfile(''' + import warnings + import os + warnings.simplefilter('always') + + def test_foo(mock, tmpdir): + mock.patch('os.listdir', return_value=['mocked']) + assert os.listdir(str(tmpdir)) == ['mocked'] + mock.stopall() + assert os.listdir(str(tmpdir)) == [] + ''') + result = testdir.runpytest('-s') + result.stderr.fnmatch_lines(['*"mock" fixture has been deprecated*']) + + +def test_deprecated_mock(mock, tmpdir): + """ + Use backward-compatibility-only mock fixture to ensure complete coverage. + """ + mock.patch('os.listdir', return_value=['mocked']) + assert os.listdir(str(tmpdir)) == ['mocked'] + mock.stopall() + assert os.listdir(str(tmpdir)) == [] + + +@pytest.mark.parametrize('name', ['MagicMock', 'PropertyMock', 'Mock', 'call', 'ANY', 'sentinel', 'mock_open']) +def test_mocker_aliases(name): + from pytest_mock import mock_module, MockFixture + + mocker = MockFixture() + assert getattr(mocker, name) is getattr(mock_module, name) + + +def test_mocker_resetall(mocker): + listdir = mocker.patch('os.listdir') + open = mocker.patch('os.open') + + listdir("/tmp") + open("/tmp/foo.txt") + listdir.assert_called_once_with("/tmp") + open.assert_called_once_with("/tmp/foo.txt") + + mocker.resetall() + + assert not listdir.called + assert not open.called + + +class TestMockerStub: + def test_call(self, mocker): + stub = mocker.stub() + stub('foo', 'bar') + stub.assert_called_once_with('foo', 'bar') + + def test_repr_with_no_name(self, mocker): + stub = mocker.stub() + assert not 'name' in repr(stub) + + def test_repr_with_name(self, mocker): + test_name = 'funny walk' + stub = mocker.stub(name=test_name) + assert "name={0!r}".format(test_name) in repr(stub) + + def __test_failure_message(self, mocker, **kwargs): + expected_name = kwargs.get('name') or 'mock' + expected_message = 'Expected call: {0}()\nNot called'.format(expected_name) + stub = mocker.stub(**kwargs) + with pytest.raises(AssertionError) as exc_info: + stub.assert_called_with() + assert str(exc_info.value) == expected_message + + def test_failure_message_with_no_name(self, mocker): + self.__test_failure_message(mocker) + + @pytest.mark.parametrize('name', (None, '', 'f', 'The Castle of aaarrrrggh')) + def test_failure_message_with_name(self, mocker, name): + self.__test_failure_message(mocker, name=name) + + +def test_instance_method_spy(mocker): + class Foo(object): + + def bar(self, arg): + return arg * 2 + + foo = Foo() + other = Foo() + spy = mocker.spy(foo, 'bar') + assert foo.bar(arg=10) == 20 + assert other.bar(arg=10) == 20 + foo.bar.assert_called_once_with(arg=10) + spy.assert_called_once_with(arg=10) + + +@skip_pypy +def test_instance_method_by_class_spy(mocker): + from pytest_mock import mock_module + + class Foo(object): + + def bar(self, arg): + return arg * 2 + + spy = mocker.spy(Foo, 'bar') + foo = Foo() + other = Foo() + assert foo.bar(arg=10) == 20 + assert other.bar(arg=10) == 20 + calls = [mock_module.call(foo, arg=10), mock_module.call(other, arg=10)] + assert spy.call_args_list == calls + + +@skip_pypy +def test_instance_method_by_subclass_spy(mocker): + from pytest_mock import mock_module + + class Base(object): + + def bar(self, arg): + return arg * 2 + + class Foo(Base): + pass + + spy = mocker.spy(Foo, 'bar') + foo = Foo() + other = Foo() + assert foo.bar(arg=10) == 20 + assert other.bar(arg=10) == 20 + calls = [mock_module.call(foo, arg=10), mock_module.call(other, arg=10)] + assert spy.call_args_list == calls + + +@skip_pypy +def test_class_method_spy(mocker): + class Foo(object): + + @classmethod + def bar(cls, arg): + return arg * 2 + + spy = mocker.spy(Foo, 'bar') + assert Foo.bar(arg=10) == 20 + Foo.bar.assert_called_once_with(arg=10) + spy.assert_called_once_with(arg=10) + + +@skip_pypy +@pytest.mark.xfail(sys.version_info[0] == 2, reason='does not work on Python 2') +def test_class_method_subclass_spy(mocker): + class Base(object): + + @classmethod + def bar(self, arg): + return arg * 2 + + class Foo(Base): + pass + + spy = mocker.spy(Foo, 'bar') + assert Foo.bar(arg=10) == 20 + Foo.bar.assert_called_once_with(arg=10) + spy.assert_called_once_with(arg=10) + + +@skip_pypy +def test_class_method_with_metaclass_spy(mocker): + class MetaFoo(type): + pass + + class Foo(object): + + __metaclass__ = MetaFoo + + @classmethod + def bar(cls, arg): + return arg * 2 + + spy = mocker.spy(Foo, 'bar') + assert Foo.bar(arg=10) == 20 + Foo.bar.assert_called_once_with(arg=10) + spy.assert_called_once_with(arg=10) + + +@skip_pypy +def test_static_method_spy(mocker): + class Foo(object): + + @staticmethod + def bar(arg): + return arg * 2 + + spy = mocker.spy(Foo, 'bar') + assert Foo.bar(arg=10) == 20 + Foo.bar.assert_called_once_with(arg=10) + spy.assert_called_once_with(arg=10) + + +@skip_pypy +@pytest.mark.xfail(sys.version_info[0] == 2, reason='does not work on Python 2') +def test_static_method_subclass_spy(mocker): + class Base(object): + + @staticmethod + def bar(arg): + return arg * 2 + + class Foo(Base): + pass + + spy = mocker.spy(Foo, 'bar') + assert Foo.bar(arg=10) == 20 + Foo.bar.assert_called_once_with(arg=10) + spy.assert_called_once_with(arg=10) + + +@contextmanager +def assert_traceback(): + """ + Assert that this file is at the top of the filtered traceback + """ + try: + yield + except AssertionError: + traceback = py.code.ExceptionInfo().traceback + crashentry = traceback.getcrashentry() + assert crashentry.path == __file__ + else: + raise AssertionError("DID NOT RAISE") + + +@contextmanager +def assert_argument_introspection(left, right): + """ + Assert detailed argument introspection is used + """ + try: + yield + except AssertionError as e: + # this may be a bit too assuming, but seems nicer then hard-coding + import _pytest.assertion.util as util + # NOTE: we assert with either verbose or not, depending on how our own + # test was run by examining sys.argv + verbose = any(a.startswith('-v') for a in sys.argv) + expected = '\n '.join(util._compare_eq_iterable(left, right, verbose)) + assert expected in str(e) + else: + raise AssertionError("DID NOT RAISE") + + +@pytest.mark.skipif(sys.version_info[:2] in [(3, 3), (3, 4)], + reason="assert_not_called not available in python 3.3 and 3.4") +def test_assert_not_called_wrapper(mocker): + stub = mocker.stub() + stub.assert_not_called() + stub() + with assert_traceback(): + stub.assert_not_called() + + +def test_assert_called_with_wrapper(mocker): + stub = mocker.stub() + stub("foo") + stub.assert_called_with("foo") + with assert_traceback(): + stub.assert_called_with("bar") + + +def test_assert_called_once_with_wrapper(mocker): + stub = mocker.stub() + stub("foo") + stub.assert_called_once_with("foo") + stub("foo") + with assert_traceback(): + stub.assert_called_once_with("foo") + + +def test_assert_called_args_with_introspection(mocker): + stub = mocker.stub() + + complex_args = ('a', 1, set(['test'])) + wrong_args = ('b', 2, set(['jest'])) + + stub(*complex_args) + stub.assert_called_with(*complex_args) + stub.assert_called_once_with(*complex_args) + + with assert_argument_introspection(complex_args, wrong_args): + stub.assert_called_with(*wrong_args) + stub.assert_called_once_with(*wrong_args) + + +def test_assert_called_kwargs_with_introspection(mocker): + stub = mocker.stub() + + complex_kwargs = dict(foo={'bar': 1, 'baz': 'spam'}) + wrong_kwargs = dict(foo={'goo': 1, 'baz': 'bran'}) + + stub(**complex_kwargs) + stub.assert_called_with(**complex_kwargs) + stub.assert_called_once_with(**complex_kwargs) + + with assert_argument_introspection(complex_kwargs, wrong_kwargs): + stub.assert_called_with(**wrong_kwargs) + stub.assert_called_once_with(**wrong_kwargs) + + +def test_assert_any_call_wrapper(mocker): + stub = mocker.stub() + stub("foo") + stub("foo") + stub.assert_any_call("foo") + with assert_traceback(): + stub.assert_any_call("bar") + + +def test_assert_has_calls(mocker): + from pytest_mock import mock_module + stub = mocker.stub() + stub("foo") + stub.assert_has_calls([mock_module.call("foo")]) + with assert_traceback(): + stub.assert_has_calls([mock_module.call("bar")]) + + +def test_monkeypatch_ini(mocker, testdir): + # Make sure the following function actually tests something + stub = mocker.stub() + assert stub.assert_called_with.__module__ != stub.__module__ + + testdir.makepyfile(""" + import py.code + def test_foo(mocker): + stub = mocker.stub() + assert stub.assert_called_with.__module__ == stub.__module__ + """) + testdir.makeini(""" + [pytest] + mock_traceback_monkeypatch = false + """) + if hasattr(testdir, 'runpytest_subprocess'): + result = testdir.runpytest_subprocess() + else: + # pytest 2.7.X + result = testdir.runpytest() + assert result.ret == 0 + + +def test_parse_ini_boolean(): + import pytest_mock + assert pytest_mock.parse_ini_boolean('True') is True + assert pytest_mock.parse_ini_boolean('false') is False + with pytest.raises(ValueError): + pytest_mock.parse_ini_boolean('foo') + + +def test_patched_method_parameter_name(mocker): + """Test that our internal code uses uncommon names when wrapping other + "mock" methods to avoid conflicts with user code (#31). + """ + + class Request: + @classmethod + def request(cls, method, args): + pass + + m = mocker.patch.object(Request, 'request') + Request.request(method='get', args={'type': 'application/json'}) + m.assert_called_once_with(method='get', args={'type': 'application/json'}) + + +def test_monkeypatch_native(testdir): + """Automatically disable monkeypatching when --tb=native. + """ + testdir.makepyfile(""" + def test_foo(mocker): + stub = mocker.stub() + stub(1, greet='hello') + stub.assert_called_once_with(1, greet='hey') + """) + if hasattr(testdir, 'runpytest_subprocess'): + result = testdir.runpytest_subprocess('--tb=native') + else: + # pytest 2.7.X + result = testdir.runpytest('--tb=native') + assert result.ret == 1 + assert 'During handling of the above exception' not in result.stdout.str() + assert 'Differing items:' not in result.stdout.str() + traceback_lines = [x for x in result.stdout.str().splitlines() + if 'Traceback (most recent call last)' in x] + assert len(traceback_lines) == 1 # make sure there are no duplicated tracebacks (#44) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..d145146 --- /dev/null +++ b/tox.ini @@ -0,0 +1,24 @@ +[tox] +envlist = py{26,27,33,34,35}-pytest{27,28,29,30},linting + +[testenv] +passenv = USER USERNAME +deps = + coverage + pytest27: pytest~=2.7 + pytest28: pytest~=2.8 + pytest29: pytest~=2.9 + pytest30: pytest~=3.0 +commands = + coverage run --append --source=pytest_mock.py -m pytest test_pytest_mock.py + +[testenv:linting] +basepython = python3.5 +skip_install=True +deps = + pytest-flakes + restructuredtext_lint + pygments +commands = + py.test --flakes pytest_mock.py test_pytest_mock.py -m flakes + rst-lint CHANGELOG.rst README.rst