pytest-mock/pytest_mock.py
2015-10-09 23:24:37 +02:00

129 lines
4.1 KiB
Python

import sys
import inspect
import pytest
if sys.version_info >= (3, 3): # pragma: no cover
import unittest.mock as mock_module
else:
import mock as mock_module
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
ANY = mock_module.ANY
def __init__(self):
self._patches = [] # list of mock._patch objects
self.patch = self._Patcher(self._patches)
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[:] = []
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
value = obj.__getattribute__(obj, name)
if isinstance(value, (classmethod, staticmethod)):
autospec = False
result = self.patch.object(obj, name, side_effect=method,
autospec=autospec)
return result
def stub(self):
"""
Creates a stub method. It accepts any arguments. Ideal to register to
callbacks in tests.
:rtype: mock.MagicMock
:return: Stub object.
"""
return mock_module.MagicMock(spec=lambda *args, **kwargs: None)
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):
self._patches = patches
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)
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