a new monkeypatch.replace(target, value) call which derives the
monkeypatch location from target (can be class/module/function or string which is taken as importable python path) examples: monkeypatch.replace(os.path.abspath, lambda x: "") monkeypatch.replace("requests.get", ...)
This commit is contained in:
parent
3fddf99661
commit
407283ef81
11
CHANGELOG
11
CHANGELOG
|
@ -1,6 +1,16 @@
|
|||
Changes between 2.3.5 and 2.4.DEV
|
||||
-----------------------------------
|
||||
|
||||
- new monkeypatch.replace() to allow for more direct patching::
|
||||
|
||||
monkeypatch.replace(os.path.abspath, lambda x: "mocked")
|
||||
|
||||
instead of: monkeypatch.setattr(os.path, "abspath", lambda x: "mocked")
|
||||
|
||||
You can also avoid imports by specifying a python path string::
|
||||
|
||||
monkeypatch.replace("requests.get", ...)
|
||||
|
||||
- fix issue322: tearDownClass is not run if setUpClass failed. Thanks
|
||||
Mathieu Agopian for the initial fix. Also make all of pytest/nose finalizer
|
||||
mimick the same generic behaviour: if a setupX exists and fails,
|
||||
|
@ -98,6 +108,7 @@ Changes between 2.3.5 and 2.4.DEV
|
|||
|
||||
- better parametrize error messages, thanks Brianna Laugher
|
||||
|
||||
|
||||
known incompatibilities:
|
||||
|
||||
- if calling --genscript from python2.7 or above, you only get a
|
||||
|
|
|
@ -26,6 +26,47 @@ def pytest_funcarg__monkeypatch(request):
|
|||
|
||||
notset = object()
|
||||
|
||||
if sys.version_info < (3,0):
|
||||
def derive_obj_and_name(obj):
|
||||
name = obj.__name__
|
||||
real_obj = getattr(obj, "im_self", None)
|
||||
if real_obj is None:
|
||||
real_obj = getattr(obj, "im_class", None)
|
||||
if real_obj is None:
|
||||
real_obj = sys.modules[obj.__module__]
|
||||
assert getattr(real_obj, name) == obj, \
|
||||
"could not derive object/name pair"
|
||||
return name, real_obj
|
||||
|
||||
else:
|
||||
def derive_obj_and_name(obj):
|
||||
name = obj.__name__
|
||||
real_obj = getattr(obj, "__self__", None)
|
||||
if real_obj is None:
|
||||
current = sys.modules[obj.__module__]
|
||||
for name in obj.__qualname__.split("."):
|
||||
real_obj = current
|
||||
current = getattr(current, name)
|
||||
assert getattr(real_obj, name) == obj, \
|
||||
"could not derive object/name pair"
|
||||
return name, real_obj
|
||||
|
||||
def derive_from_string(target):
|
||||
rest = []
|
||||
while target:
|
||||
try:
|
||||
obj = __import__(target, None, None, "__doc__")
|
||||
except ImportError:
|
||||
if "." not in target:
|
||||
raise
|
||||
target, name = target.rsplit(".", 1)
|
||||
rest.append(name)
|
||||
else:
|
||||
assert len(rest) >= 1
|
||||
while len(rest) != 1:
|
||||
obj = getattr(obj, rest.pop())
|
||||
return rest[0], obj
|
||||
|
||||
class monkeypatch:
|
||||
""" object keeping a record of setattr/item/env/syspath changes. """
|
||||
def __init__(self):
|
||||
|
@ -33,9 +74,28 @@ class monkeypatch:
|
|||
self._setitem = []
|
||||
self._cwd = None
|
||||
|
||||
def replace(self, target, value):
|
||||
""" derive monkeypatching location from ``target`` and call
|
||||
setattr(derived_obj, derived_name, value).
|
||||
|
||||
This function can usually derive monkeypatch locations
|
||||
for function, method or class targets. It also accepts
|
||||
a string which is taken as a python path which is then
|
||||
tried to be imported. For example the target "os.path.abspath"
|
||||
will lead to a call to setattr(os.path, "abspath", value)
|
||||
without the need to import "os.path" yourself.
|
||||
"""
|
||||
if isinstance(target, str):
|
||||
name, obj = derive_from_string(target)
|
||||
else:
|
||||
name, obj = derive_obj_and_name(target)
|
||||
return self.setattr(obj, name, value)
|
||||
|
||||
def setattr(self, obj, name, value, raising=True):
|
||||
""" set attribute ``name`` on ``obj`` to ``value``, by default
|
||||
raise AttributeEror if the attribute did not exist. """
|
||||
raise AttributeEror if the attribute did not exist.
|
||||
|
||||
"""
|
||||
oldval = getattr(obj, name, notset)
|
||||
if raising and oldval is notset:
|
||||
raise AttributeError("%r has no attribute %r" %(obj, name))
|
||||
|
|
|
@ -29,7 +29,7 @@ patch this function before calling into a function which uses it::
|
|||
def test_mytest(monkeypatch):
|
||||
def mockreturn(path):
|
||||
return '/abc'
|
||||
monkeypatch.setattr(os.path, 'expanduser', mockreturn)
|
||||
monkeypatch.setattr(os.path., 'expanduser', mockreturn)
|
||||
x = getssh()
|
||||
assert x == '/abc/.ssh'
|
||||
|
||||
|
@ -41,7 +41,7 @@ Method reference of the monkeypatch function argument
|
|||
-----------------------------------------------------
|
||||
|
||||
.. autoclass:: monkeypatch
|
||||
:members: setattr, delattr, setitem, delitem, setenv, delenv, syspath_prepend, chdir, undo
|
||||
:members: setattr, replace, delattr, setitem, delitem, setenv, delenv, syspath_prepend, chdir, undo
|
||||
|
||||
``monkeypatch.setattr/delattr/delitem/delenv()`` all
|
||||
by default raise an Exception if the target does not exist.
|
||||
|
|
|
@ -35,6 +35,38 @@ def test_setattr():
|
|||
monkeypatch.undo() # double-undo makes no modification
|
||||
assert A.x == 5
|
||||
|
||||
class TestDerived:
|
||||
def f(self):
|
||||
pass
|
||||
|
||||
def test_class_function(self, monkeypatch):
|
||||
monkeypatch.replace(TestDerived.f, lambda x: 42)
|
||||
assert TestDerived().f() == 42
|
||||
|
||||
def test_instance_function(self, monkeypatch):
|
||||
t = TestDerived()
|
||||
monkeypatch.replace(t.f, lambda: 42)
|
||||
assert t.f() == 42
|
||||
|
||||
def test_module_class(self, monkeypatch):
|
||||
class New:
|
||||
pass
|
||||
monkeypatch.replace(TestDerived, New)
|
||||
assert TestDerived == New
|
||||
|
||||
def test_nested_module(self, monkeypatch):
|
||||
monkeypatch.replace(os.path.abspath, lambda x: "hello")
|
||||
assert os.path.abspath("123") == "hello"
|
||||
|
||||
def test_string_expression(self, monkeypatch):
|
||||
monkeypatch.replace("os.path.abspath", lambda x: "hello2")
|
||||
assert os.path.abspath("123") == "hello2"
|
||||
|
||||
def test_string_expression_class(self, monkeypatch):
|
||||
monkeypatch.replace("_pytest.config.Config", 42)
|
||||
import _pytest
|
||||
assert _pytest.config.Config == 42
|
||||
|
||||
def test_delattr():
|
||||
class A:
|
||||
x = 1
|
||||
|
|
Loading…
Reference in New Issue