diff --git a/LICENSE b/LICENSE
index 1dd90f2..72a8093 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,165 +1,21 @@
-GNU LESSER GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
+MIT License
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
+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:
- This version of the GNU Lesser General Public License incorporates
-the terms and conditions of version 3 of the GNU General Public
-License, supplemented by the additional permissions listed below.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
- 0. Additional Definitions.
-
- As used herein, "this License" refers to version 3 of the GNU Lesser
-General Public License, and the "GNU GPL" refers to version 3 of the GNU
-General Public License.
-
- "The Library" refers to a covered work governed by this License,
-other than an Application or a Combined Work as defined below.
-
- An "Application" is any work that makes use of an interface provided
-by the Library, but which is not otherwise based on the Library.
-Defining a subclass of a class defined by the Library is deemed a mode
-of using an interface provided by the Library.
-
- A "Combined Work" is a work produced by combining or linking an
-Application with the Library. The particular version of the Library
-with which the Combined Work was made is also called the "Linked
-Version".
-
- The "Minimal Corresponding Source" for a Combined Work means the
-Corresponding Source for the Combined Work, excluding any source code
-for portions of the Combined Work that, considered in isolation, are
-based on the Application, and not on the Linked Version.
-
- The "Corresponding Application Code" for a Combined Work means the
-object code and/or source code for the Application, including any data
-and utility programs needed for reproducing the Combined Work from the
-Application, but excluding the System Libraries of the Combined Work.
-
- 1. Exception to Section 3 of the GNU GPL.
-
- You may convey a covered work under sections 3 and 4 of this License
-without being bound by section 3 of the GNU GPL.
-
- 2. Conveying Modified Versions.
-
- If you modify a copy of the Library, and, in your modifications, a
-facility refers to a function or data to be supplied by an Application
-that uses the facility (other than as an argument passed when the
-facility is invoked), then you may convey a copy of the modified
-version:
-
- a) under this License, provided that you make a good faith effort to
- ensure that, in the event an Application does not supply the
- function or data, the facility still operates, and performs
- whatever part of its purpose remains meaningful, or
-
- b) under the GNU GPL, with none of the additional permissions of
- this License applicable to that copy.
-
- 3. Object Code Incorporating Material from Library Header Files.
-
- The object code form of an Application may incorporate material from
-a header file that is part of the Library. You may convey such object
-code under terms of your choice, provided that, if the incorporated
-material is not limited to numerical parameters, data structure
-layouts and accessors, or small macros, inline functions and templates
-(ten or fewer lines in length), you do both of the following:
-
- a) Give prominent notice with each copy of the object code that the
- Library is used in it and that the Library and its use are
- covered by this License.
-
- b) Accompany the object code with a copy of the GNU GPL and this license
- document.
-
- 4. Combined Works.
-
- You may convey a Combined Work under terms of your choice that,
-taken together, effectively do not restrict modification of the
-portions of the Library contained in the Combined Work and reverse
-engineering for debugging such modifications, if you also do each of
-the following:
-
- a) Give prominent notice with each copy of the Combined Work that
- the Library is used in it and that the Library and its use are
- covered by this License.
-
- b) Accompany the Combined Work with a copy of the GNU GPL and this license
- document.
-
- c) For a Combined Work that displays copyright notices during
- execution, include the copyright notice for the Library among
- these notices, as well as a reference directing the user to the
- copies of the GNU GPL and this license document.
-
- d) Do one of the following:
-
- 0) Convey the Minimal Corresponding Source under the terms of this
- License, and the Corresponding Application Code in a form
- suitable for, and under terms that permit, the user to
- recombine or relink the Application with a modified version of
- the Linked Version to produce a modified Combined Work, in the
- manner specified by section 6 of the GNU GPL for conveying
- Corresponding Source.
-
- 1) Use a suitable shared library mechanism for linking with the
- Library. A suitable mechanism is one that (a) uses at run time
- a copy of the Library already present on the user's computer
- system, and (b) will operate properly with a modified version
- of the Library that is interface-compatible with the Linked
- Version.
-
- e) Provide Installation Information, but only if you would otherwise
- be required to provide such information under section 6 of the
- GNU GPL, and only to the extent that such information is
- necessary to install and execute a modified version of the
- Combined Work produced by recombining or relinking the
- Application with a modified version of the Linked Version. (If
- you use option 4d0, the Installation Information must accompany
- the Minimal Corresponding Source and Corresponding Application
- Code. If you use option 4d1, you must provide the Installation
- Information in the manner specified by section 6 of the GNU GPL
- for conveying Corresponding Source.)
-
- 5. Combined Libraries.
-
- You may place library facilities that are a work based on the
-Library side by side in a single library together with other library
-facilities that are not Applications and are not covered by this
-License, and convey such a combined library under terms of your
-choice, if you do both of the following:
-
- a) Accompany the combined library with a copy of the same work based
- on the Library, uncombined with any other library facilities,
- conveyed under the terms of this License.
-
- b) Give prominent notice with the combined library that part of it
- is a work based on the Library, and explaining where to find the
- accompanying uncombined form of the same work.
-
- 6. Revised Versions of the GNU Lesser General Public License.
-
- The Free Software Foundation may publish revised and/or new versions
-of the GNU Lesser General Public License from time to time. Such new
-versions will be similar in spirit to the present version, but may
-differ in detail to address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Library as you received it specifies that a certain numbered version
-of the GNU Lesser General Public License "or any later version"
-applies to it, you have the option of following the terms and
-conditions either of that published version or of any later version
-published by the Free Software Foundation. If the Library as you
-received it does not specify a version number of the GNU Lesser
-General Public License, you may choose any version of the GNU Lesser
-General Public License ever published by the Free Software Foundation.
-
- If the Library as you received it specifies that a proxy can decide
-whether future versions of the GNU Lesser General Public License shall
-apply, that proxy's public statement of acceptance of any version is
-permanent authorization for you to choose that version for the
-Library.
\ No newline at end of file
+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/PKG-INFO b/PKG-INFO
index 47b3137..1ee9ff6 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,55 +1,56 @@
Metadata-Version: 1.1
Name: pytest-mock
-Version: 0.8.1
+Version: 1.1
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: LGPL
+License: MIT
Description: ===========
pytest-mock
===========
This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API
- provided by the excellent `mock `_ package,
+ 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| |coverage|
+ |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://pypip.in/py_versions/pytest-mock/badge.png
+ .. |python| image:: https://img.shields.io/pypi/pyversions/pytest-mock.svg
:target: https://pypi.python.org/pypi/pytest-mock/
- :alt: Supported Python versions
-
Usage
=====
The ``mocker`` fixture has the same API as
- `mock.patch `_,
+ `mock.patch `_,
supporting the same arguments:
.. code-block:: python
@@ -59,16 +60,187 @@ Description: ===========
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 at this point.
+ * ``mocker.stopall()``: stops all active patches up to this point.
+ * ``mocker.resetall()``: calls ``reset_mock()`` in all mocked objects up to this point.
- Note that, 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:
+ Some objects from the ``mock`` module are accessible directly from ``mocker`` for convenience:
+
+ * `Mock `_
+ * `MagicMock `_
+ * `PropertyMock `_
+ * `ANY `_
+ * `call `_ *(Version 1.1)*
+
+
+ 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
@@ -86,154 +258,24 @@ Description: ===========
E AttributeError: __exit__
- You can also access ``Mock`` and ``MagicMock`` directly using from ``mocker``
- fixture:
-
- .. code-block:: python
-
- def test_feature(mocker):
- ret = [mocker.Mock(return_value=True), mocker.Mock(return_value=True)]
- mocker.patch('mylib.func', side_effect=ret)
-
- *New in version 0.5*
-
- Spy
- ---
-
- *New in version 0.6*
-
- The spy acts exactly like the original method in all cases, except it allows use of `mock`
- features with it, like retrieving call count.
-
- From version 0.7 onward it also works for class and static methods. Originally it was only safe to
- use with instance 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
- ----
-
- *New in version 0.6*
-
- The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance.
-
- .. code-block:: python
-
- def test_stub(mocker):
- def foo(on_something):
- on_something('foo', 'bar')
-
- stub = mocker.stub()
-
- foo(stub)
- stub.assert_called_once_with('foo', 'bar')
-
- Note
- ----
-
- Prior to version ``0.4.0``, the ``mocker`` fixture was named ``mock``.
- This was changed because naming the fixture ``mock`` conflicts with the
- actual ``mock`` module, which made using it awkward when access to both the
- module and the plugin were required within a test.
-
- The old fixture ``mock`` still works, but its use is discouraged and will be
- removed in version ``1.0``.
-
- Requirements
- ============
-
- * Python 2.6+, Python 3.2+
- * pytest
- * mock (for Python < 3.3)
-
-
- Install
+ License
=======
- Install using `pip `_:
+ Distributed under the terms of the `MIT`_ license.
- .. code-block:: console
-
- $ pip install pytest-mock
-
- Changelog
- =========
-
- Please consult `releases `_.
-
- 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;
+ .. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE
Keywords: pytest mock
Platform: any
-Classifier: Development Status :: 3 - Alpha
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Framework :: Pytest
Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
+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
diff --git a/README.rst b/README.rst
index 28c02d7..d0d3033 100644
--- a/README.rst
+++ b/README.rst
@@ -3,45 +3,46 @@ pytest-mock
===========
This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API
-provided by the excellent `mock `_ package,
+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| |coverage|
+|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://pypip.in/py_versions/pytest-mock/badge.png
+.. |python| image:: https://img.shields.io/pypi/pyversions/pytest-mock.svg
:target: https://pypi.python.org/pypi/pytest-mock/
- :alt: Supported Python versions
-
Usage
=====
The ``mocker`` fixture has the same API as
-`mock.patch `_,
+`mock.patch `_,
supporting the same arguments:
.. code-block:: python
@@ -51,16 +52,187 @@ supporting the same arguments:
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 at this point.
+* ``mocker.stopall()``: stops all active patches up to this point.
+* ``mocker.resetall()``: calls ``reset_mock()`` in all mocked objects up to this point.
-Note that, 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:
+Some objects from the ``mock`` module are accessible directly from ``mocker`` for convenience:
+
+* `Mock `_
+* `MagicMock `_
+* `PropertyMock `_
+* `ANY `_
+* `call `_ *(Version 1.1)*
+
+
+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
@@ -78,141 +250,9 @@ Note that, although mocker's API is intentionally the same as ``mock.patch``'s,
E AttributeError: __exit__
-You can also access ``Mock`` and ``MagicMock`` directly using from ``mocker``
-fixture:
-
-.. code-block:: python
-
- def test_feature(mocker):
- ret = [mocker.Mock(return_value=True), mocker.Mock(return_value=True)]
- mocker.patch('mylib.func', side_effect=ret)
-
-*New in version 0.5*
-
-Spy
----
-
-*New in version 0.6*
-
-The spy acts exactly like the original method in all cases, except it allows use of `mock`
-features with it, like retrieving call count.
-
-From version 0.7 onward it also works for class and static methods. Originally it was only safe to
-use with instance 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
-----
-
-*New in version 0.6*
-
-The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance.
-
-.. code-block:: python
-
- def test_stub(mocker):
- def foo(on_something):
- on_something('foo', 'bar')
-
- stub = mocker.stub()
-
- foo(stub)
- stub.assert_called_once_with('foo', 'bar')
-
-Note
-----
-
-Prior to version ``0.4.0``, the ``mocker`` fixture was named ``mock``.
-This was changed because naming the fixture ``mock`` conflicts with the
-actual ``mock`` module, which made using it awkward when access to both the
-module and the plugin were required within a test.
-
-The old fixture ``mock`` still works, but its use is discouraged and will be
-removed in version ``1.0``.
-
-Requirements
-============
-
-* Python 2.6+, Python 3.2+
-* pytest
-* mock (for Python < 3.3)
-
-
-Install
+License
=======
-Install using `pip `_:
+Distributed under the terms of the `MIT`_ license.
-.. code-block:: console
-
- $ pip install pytest-mock
-
-Changelog
-=========
-
-Please consult `releases `_.
-
-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;
+.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE
diff --git a/pytest_mock.egg-info/PKG-INFO b/pytest_mock.egg-info/PKG-INFO
new file mode 100644
index 0000000..1ee9ff6
--- /dev/null
+++ b/pytest_mock.egg-info/PKG-INFO
@@ -0,0 +1,281 @@
+Metadata-Version: 1.1
+Name: pytest-mock
+Version: 1.1
+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)*
+
+
+ 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
diff --git a/pytest_mock.egg-info/SOURCES.txt b/pytest_mock.egg-info/SOURCES.txt
new file mode 100644
index 0000000..0a429db
--- /dev/null
+++ b/pytest_mock.egg-info/SOURCES.txt
@@ -0,0 +1,13 @@
+LICENSE
+MANIFEST.in
+README.rst
+pytest_mock.py
+setup.cfg
+setup.py
+test_pytest_mock.py
+pytest_mock.egg-info/PKG-INFO
+pytest_mock.egg-info/SOURCES.txt
+pytest_mock.egg-info/dependency_links.txt
+pytest_mock.egg-info/entry_points.txt
+pytest_mock.egg-info/requires.txt
+pytest_mock.egg-info/top_level.txt
\ No newline at end of file
diff --git a/pytest_mock.egg-info/dependency_links.txt b/pytest_mock.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pytest_mock.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/pytest_mock.egg-info/entry_points.txt b/pytest_mock.egg-info/entry_points.txt
new file mode 100644
index 0000000..abb7b36
--- /dev/null
+++ b/pytest_mock.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[pytest11]
+pytest_mock = pytest_mock
+
diff --git a/pytest_mock.egg-info/requires.txt b/pytest_mock.egg-info/requires.txt
new file mode 100644
index 0000000..79ee644
--- /dev/null
+++ b/pytest_mock.egg-info/requires.txt
@@ -0,0 +1,4 @@
+pytest>=2.7
+
+[:python_version=="2.6" or python_version=="2.7"]
+mock
diff --git a/pytest_mock.egg-info/top_level.txt b/pytest_mock.egg-info/top_level.txt
new file mode 100644
index 0000000..59bf78d
--- /dev/null
+++ b/pytest_mock.egg-info/top_level.txt
@@ -0,0 +1 @@
+pytest_mock
diff --git a/pytest_mock.py b/pytest_mock.py
index 9f06cd6..7c8ebfc 100644
--- a/pytest_mock.py
+++ b/pytest_mock.py
@@ -1,14 +1,15 @@
-import sys
import inspect
+import sys
import pytest
-
if sys.version_info >= (3, 3): # pragma: no cover
import unittest.mock as mock_module
else:
import mock as mock_module
+version = '1.1'
+
class MockFixture(object):
"""
@@ -18,11 +19,21 @@ class MockFixture(object):
Mock = mock_module.Mock
MagicMock = mock_module.MagicMock
+ PropertyMock = mock_module.PropertyMock
+ call = mock_module.call
ANY = mock_module.ANY
def __init__(self):
self._patches = [] # list of mock._patch objects
- self.patch = self._Patcher(self._patches)
+ 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):
"""
@@ -32,6 +43,7 @@ class MockFixture(object):
for p in reversed(self._patches):
p.stop()
self._patches[:] = []
+ self._mocks[:] = []
def spy(self, obj, name):
"""
@@ -49,25 +61,30 @@ class MockFixture(object):
# Can't use autospec classmethod or staticmethod objects
# see: https://bugs.python.org/issue23078
if inspect.isclass(obj):
- # bypass class descriptor:
+ # Bypass class descriptor:
# http://stackoverflow.com/questions/14187973/python3-check-if-method-is-static
- value = obj.__getattribute__(obj, name)
- if isinstance(value, (classmethod, staticmethod)):
- autospec = False
+ 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):
+ 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)
+ return mock_module.MagicMock(spec=lambda *args, **kwargs: None, name=name)
class _Patcher(object):
"""
@@ -75,8 +92,9 @@ class MockFixture(object):
etc. We need this indirection to keep the same API of the mock package.
"""
- def __init__(self, patches):
+ 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
@@ -86,6 +104,7 @@ class MockFixture(object):
p = mock_func(*args, **kwargs)
mocked = p.start()
self._patches.append(p)
+ self._mocks.append(mocked)
return mocked
def object(self, *args, **kwargs):
@@ -126,3 +145,114 @@ def mock(mocker):
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)
diff --git a/setup.py b/setup.py
index 3ca9b55..6a8e126 100644
--- a/setup.py
+++ b/setup.py
@@ -1,35 +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='0.8.1',
+ version=version,
entry_points={
'pytest11': ['pytest_mock = pytest_mock'],
},
py_modules=['pytest_mock'],
platforms='any',
install_requires=[
- 'pytest>=2.4',
+ 'pytest>=2.7',
],
extras_require={
':python_version=="2.6" or python_version=="2.7"': ['mock'],
},
url='https://github.com/pytest-dev/pytest-mock/',
- license='LGPL',
+ 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 :: 3 - Alpha',
+ 'Development Status :: 5 - Production/Stable',
+ 'Framework :: Pytest',
'Intended Audience :: Developers',
- 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
+ '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',
diff --git a/test_pytest_mock.py b/test_pytest_mock.py
index 7caf17d..3980462 100644
--- a/test_pytest_mock.py
+++ b/test_pytest_mock.py
@@ -1,9 +1,12 @@
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!
@@ -129,28 +132,58 @@ def test_deprecated_mock(mock, tmpdir):
assert os.listdir(str(tmpdir)) == []
-def test_mocker_has_magic_mock_class_as_attribute_for_instantiation():
+@pytest.mark.parametrize('name', ['MagicMock', 'PropertyMock', 'Mock', 'call', 'ANY'])
+def test_mocker_aliases(name):
from pytest_mock import mock_module, MockFixture
mocker = MockFixture()
- assert isinstance(mocker.MagicMock(), mock_module.MagicMock)
+ assert getattr(mocker, name) is getattr(mock_module, name)
-def test_mocker_has_mock_class_as_attribute_for_instantiation():
- from pytest_mock import mock_module, MockFixture
+def test_mocker_resetall(mocker):
+ listdir = mocker.patch('os.listdir')
+ open = mocker.patch('os.open')
- mocker = MockFixture()
- assert isinstance(mocker.Mock(), mock_module.Mock)
+ 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
-def test_mocker_stub(mocker):
- def foo(on_something):
- on_something('foo', 'bar')
+class TestMockerStub:
+ def test_call(self, mocker):
+ stub = mocker.stub()
+ stub('foo', 'bar')
+ stub.assert_called_once_with('foo', 'bar')
- stub = mocker.stub()
+ def test_repr_with_no_name(self, mocker):
+ stub = mocker.stub()
+ assert not 'name' in repr(stub)
- foo(stub)
- stub.assert_called_once_with('foo', 'bar')
+ 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):
@@ -186,6 +219,27 @@ def test_instance_method_by_class_spy(mocker):
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):
@@ -200,6 +254,24 @@ def test_class_method_spy(mocker):
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):
@@ -231,3 +303,198 @@ def test_static_method_spy(mocker):
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(testdir):
+ 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)