plugin.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. from __future__ import unicode_literals
  2. import functools
  3. import inspect
  4. import sys
  5. import pytest
  6. import six
  7. from ._version import version
  8. __version__ = version
  9. # pseudo-six; if this starts to require more than this, depend on six already
  10. if sys.version_info[0] == 2: # pragma: no cover
  11. text_type = unicode # noqa
  12. else:
  13. text_type = str
  14. def _get_mock_module(config):
  15. """
  16. Import and return the actual "mock" module. By default this is "mock" for Python 2 and
  17. "unittest.mock" for Python 3, but the user can force to always use "mock" on Python 3 using
  18. the mock_use_standalone_module ini option.
  19. """
  20. if not hasattr(_get_mock_module, "_module"):
  21. use_standalone_module = parse_ini_boolean(
  22. config.getini("mock_use_standalone_module")
  23. )
  24. if sys.version_info[0] == 2 or use_standalone_module:
  25. import mock
  26. _get_mock_module._module = mock
  27. else:
  28. import unittest.mock
  29. _get_mock_module._module = unittest.mock
  30. return _get_mock_module._module
  31. class MockFixture(object):
  32. """
  33. Fixture that provides the same interface to functions in the mock module,
  34. ensuring that they are uninstalled at the end of each test.
  35. """
  36. def __init__(self, config):
  37. self._patches = [] # list of mock._patch objects
  38. self._mocks = [] # list of MagicMock objects
  39. self.mock_module = mock_module = _get_mock_module(config)
  40. self.patch = self._Patcher(self._patches, self._mocks, mock_module)
  41. # aliases for convenience
  42. self.Mock = mock_module.Mock
  43. self.MagicMock = mock_module.MagicMock
  44. self.NonCallableMock = mock_module.NonCallableMock
  45. self.PropertyMock = mock_module.PropertyMock
  46. self.call = mock_module.call
  47. self.ANY = mock_module.ANY
  48. self.DEFAULT = mock_module.DEFAULT
  49. self.create_autospec = mock_module.create_autospec
  50. self.sentinel = mock_module.sentinel
  51. self.mock_open = mock_module.mock_open
  52. def resetall(self):
  53. """
  54. Call reset_mock() on all patchers started by this fixture.
  55. """
  56. for m in self._mocks:
  57. m.reset_mock()
  58. def stopall(self):
  59. """
  60. Stop all patchers started by this fixture. Can be safely called multiple
  61. times.
  62. """
  63. for p in reversed(self._patches):
  64. p.stop()
  65. self._patches[:] = []
  66. self._mocks[:] = []
  67. def spy(self, obj, name):
  68. """
  69. Creates a spy of method. It will run method normally, but it is now
  70. possible to use `mock` call features with it, like call count.
  71. :param object obj: An object.
  72. :param unicode name: A method in object.
  73. :rtype: mock.MagicMock
  74. :return: Spy object.
  75. """
  76. method = getattr(obj, name)
  77. autospec = inspect.ismethod(method) or inspect.isfunction(method)
  78. # Can't use autospec classmethod or staticmethod objects
  79. # see: https://bugs.python.org/issue23078
  80. if inspect.isclass(obj):
  81. # Bypass class descriptor:
  82. # http://stackoverflow.com/questions/14187973/python3-check-if-method-is-static
  83. try:
  84. value = obj.__getattribute__(obj, name)
  85. except AttributeError:
  86. pass
  87. else:
  88. if isinstance(value, (classmethod, staticmethod)):
  89. autospec = False
  90. if sys.version_info[0] == 2:
  91. assigned = [x for x in functools.WRAPPER_ASSIGNMENTS if hasattr(method, x)]
  92. w = functools.wraps(method, assigned=assigned)
  93. else:
  94. w = functools.wraps(method)
  95. @w
  96. def wrapper(*args, **kwargs):
  97. spy_obj.spy_return = None
  98. spy_obj.spy_exception = None
  99. try:
  100. r = method(*args, **kwargs)
  101. except Exception as e:
  102. spy_obj.spy_exception = e
  103. raise
  104. else:
  105. spy_obj.spy_return = r
  106. return r
  107. spy_obj = self.patch.object(obj, name, side_effect=wrapper, autospec=autospec)
  108. spy_obj.spy_return = None
  109. spy_obj.spy_exception = None
  110. return spy_obj
  111. def stub(self, name=None):
  112. """
  113. Creates a stub method. It accepts any arguments. Ideal to register to
  114. callbacks in tests.
  115. :param name: the constructed stub's name as used in repr
  116. :rtype: mock.MagicMock
  117. :return: Stub object.
  118. """
  119. return self.mock_module.MagicMock(spec=lambda *args, **kwargs: None, name=name)
  120. class _Patcher(object):
  121. """
  122. Object to provide the same interface as mock.patch, mock.patch.object,
  123. etc. We need this indirection to keep the same API of the mock package.
  124. """
  125. def __init__(self, patches, mocks, mock_module):
  126. self._patches = patches
  127. self._mocks = mocks
  128. self.mock_module = mock_module
  129. def _start_patch(self, mock_func, *args, **kwargs):
  130. """Patches something by calling the given function from the mock
  131. module, registering the patch to stop it later and returns the
  132. mock object resulting from the mock call.
  133. """
  134. self._enforce_no_with_context(inspect.stack())
  135. p = mock_func(*args, **kwargs)
  136. mocked = p.start()
  137. self._patches.append(p)
  138. if hasattr(mocked, "reset_mock"):
  139. self._mocks.append(mocked)
  140. return mocked
  141. def _enforce_no_with_context(self, stack):
  142. """raises a ValueError if mocker is used in a with context"""
  143. caller = stack[2]
  144. frame = caller[0]
  145. info = inspect.getframeinfo(frame)
  146. if info.code_context is None:
  147. # no source code available (#169)
  148. return
  149. code_context = " ".join(six.ensure_text(x) for x in info.code_context).strip()
  150. if code_context.startswith("with mocker."):
  151. raise ValueError(
  152. "Using mocker in a with context is not supported. "
  153. "https://github.com/pytest-dev/pytest-mock#note-about-usage-as-context-manager"
  154. )
  155. def object(self, *args, **kwargs):
  156. """API to mock.patch.object"""
  157. return self._start_patch(self.mock_module.patch.object, *args, **kwargs)
  158. def multiple(self, *args, **kwargs):
  159. """API to mock.patch.multiple"""
  160. return self._start_patch(self.mock_module.patch.multiple, *args, **kwargs)
  161. def dict(self, *args, **kwargs):
  162. """API to mock.patch.dict"""
  163. return self._start_patch(self.mock_module.patch.dict, *args, **kwargs)
  164. def __call__(self, *args, **kwargs):
  165. """API to mock.patch"""
  166. return self._start_patch(self.mock_module.patch, *args, **kwargs)
  167. @pytest.yield_fixture
  168. def mocker(pytestconfig):
  169. """
  170. return an object that has the same interface to the `mock` module, but
  171. takes care of automatically undoing all patches after each test method.
  172. """
  173. result = MockFixture(pytestconfig)
  174. yield result
  175. result.stopall()
  176. _mock_module_patches = []
  177. _mock_module_originals = {}
  178. def assert_wrapper(__wrapped_mock_method__, *args, **kwargs):
  179. __tracebackhide__ = True
  180. try:
  181. __wrapped_mock_method__(*args, **kwargs)
  182. return
  183. except AssertionError as e:
  184. if getattr(e, "_mock_introspection_applied", 0):
  185. msg = text_type(e)
  186. else:
  187. __mock_self = args[0]
  188. msg = text_type(e)
  189. if __mock_self.call_args is not None:
  190. actual_args, actual_kwargs = __mock_self.call_args
  191. introspection = ""
  192. try:
  193. assert actual_args == args[1:]
  194. except AssertionError as e:
  195. introspection += "\nArgs:\n" + text_type(e)
  196. try:
  197. assert actual_kwargs == kwargs
  198. except AssertionError as e:
  199. introspection += "\nKwargs:\n" + text_type(e)
  200. if introspection:
  201. msg += "\n\npytest introspection follows:\n" + introspection
  202. e = AssertionError(msg)
  203. e._mock_introspection_applied = True
  204. raise e
  205. def wrap_assert_not_called(*args, **kwargs):
  206. __tracebackhide__ = True
  207. assert_wrapper(_mock_module_originals["assert_not_called"], *args, **kwargs)
  208. def wrap_assert_called_with(*args, **kwargs):
  209. __tracebackhide__ = True
  210. assert_wrapper(_mock_module_originals["assert_called_with"], *args, **kwargs)
  211. def wrap_assert_called_once(*args, **kwargs):
  212. __tracebackhide__ = True
  213. assert_wrapper(_mock_module_originals["assert_called_once"], *args, **kwargs)
  214. def wrap_assert_called_once_with(*args, **kwargs):
  215. __tracebackhide__ = True
  216. assert_wrapper(_mock_module_originals["assert_called_once_with"], *args, **kwargs)
  217. def wrap_assert_has_calls(*args, **kwargs):
  218. __tracebackhide__ = True
  219. assert_wrapper(_mock_module_originals["assert_has_calls"], *args, **kwargs)
  220. def wrap_assert_any_call(*args, **kwargs):
  221. __tracebackhide__ = True
  222. assert_wrapper(_mock_module_originals["assert_any_call"], *args, **kwargs)
  223. def wrap_assert_called(*args, **kwargs):
  224. __tracebackhide__ = True
  225. assert_wrapper(_mock_module_originals["assert_called"], *args, **kwargs)
  226. def wrap_assert_methods(config):
  227. """
  228. Wrap assert methods of mock module so we can hide their traceback and
  229. add introspection information to specified argument asserts.
  230. """
  231. # Make sure we only do this once
  232. if _mock_module_originals:
  233. return
  234. mock_module = _get_mock_module(config)
  235. wrappers = {
  236. "assert_called": wrap_assert_called,
  237. "assert_called_once": wrap_assert_called_once,
  238. "assert_called_with": wrap_assert_called_with,
  239. "assert_called_once_with": wrap_assert_called_once_with,
  240. "assert_any_call": wrap_assert_any_call,
  241. "assert_has_calls": wrap_assert_has_calls,
  242. "assert_not_called": wrap_assert_not_called,
  243. }
  244. for method, wrapper in wrappers.items():
  245. try:
  246. original = getattr(mock_module.NonCallableMock, method)
  247. except AttributeError: # pragma: no cover
  248. continue
  249. _mock_module_originals[method] = original
  250. patcher = mock_module.patch.object(mock_module.NonCallableMock, method, wrapper)
  251. patcher.start()
  252. _mock_module_patches.append(patcher)
  253. if hasattr(config, "add_cleanup"):
  254. add_cleanup = config.add_cleanup
  255. else:
  256. # pytest 2.7 compatibility
  257. add_cleanup = config._cleanup.append
  258. add_cleanup(unwrap_assert_methods)
  259. def unwrap_assert_methods():
  260. for patcher in _mock_module_patches:
  261. try:
  262. patcher.stop()
  263. except RuntimeError as e:
  264. # a patcher might have been stopped by user code (#137)
  265. # so we need to catch this error here and ignore it;
  266. # unfortunately there's no public API to check if a patch
  267. # has been started, so catching the error it is
  268. if text_type(e) == "stop called on unstarted patcher":
  269. pass
  270. else:
  271. raise
  272. _mock_module_patches[:] = []
  273. _mock_module_originals.clear()
  274. def pytest_addoption(parser):
  275. parser.addini(
  276. "mock_traceback_monkeypatch",
  277. "Monkeypatch the mock library to improve reporting of the "
  278. "assert_called_... methods",
  279. default=True,
  280. )
  281. parser.addini(
  282. "mock_use_standalone_module",
  283. 'Use standalone "mock" (from PyPI) instead of builtin "unittest.mock" '
  284. "on Python 3",
  285. default=False,
  286. )
  287. def parse_ini_boolean(value):
  288. if value in (True, False):
  289. return value
  290. try:
  291. return {"true": True, "false": False}[value.lower()]
  292. except KeyError:
  293. raise ValueError("unknown string for bool: %r" % value)
  294. def pytest_configure(config):
  295. tb = config.getoption("--tb", default="auto")
  296. if (
  297. parse_ini_boolean(config.getini("mock_traceback_monkeypatch"))
  298. and tb != "native"
  299. ):
  300. wrap_assert_methods(config)