1 new commit in pytest:
https://bitbucket.org/hpk42/pytest/commits/7f5dba9095f4/
Changeset: 7f5dba9095f4
User: hpk42
Date: 2013-08-07 16:49:29
Summary: monkeypatch.replace() now only accepts a string. Improved error
handling and
docs thanks to suggestions from flub, pelme, schmir, ronny.
Affected #: 4 files
diff -r b4cd6235587f2259678b618c1c5a429babe2a2c5 -r
7f5dba9095f4d77f06c46f19b4c7f4026e6182bd CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,15 +1,12 @@
Changes between 2.3.5 and 2.4.DEV
-----------------------------------
-- new monkeypatch.replace() to allow for more direct patching::
+- new monkeypatch.replace() to avoid imports and provide a shorter
+ invocation for patching out classes/functions from modules:
- monkeypatch.replace(os.path.abspath, lambda x: "mocked")
+ monkeypatch.replace("requests.get", myfunc
- 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", ...)
+ will replace the "get" function of the "requests" module with ``myfunc``.
- fix issue322: tearDownClass is not run if setUpClass failed. Thanks
Mathieu Agopian for the initial fix. Also make all of pytest/nose finalizer
diff -r b4cd6235587f2259678b618c1c5a429babe2a2c5 -r
7f5dba9095f4d77f06c46f19b4c7f4026e6182bd _pytest/monkeypatch.py
--- a/_pytest/monkeypatch.py
+++ b/_pytest/monkeypatch.py
@@ -1,6 +1,7 @@
""" monkeypatching and mocking functionality. """
import os, sys, inspect
+import pytest
def pytest_funcarg__monkeypatch(request):
"""The returned ``monkeypatch`` funcarg provides these
@@ -26,47 +27,6 @@
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):
@@ -74,22 +34,45 @@
self._setitem = []
self._cwd = None
- def replace(self, target, value):
- """ derive monkeypatching location from ``target`` and call
- setattr(derived_obj, derived_name, value).
+ def replace(self, import_path, value):
+ """ replace the object specified by a dotted ``import_path``
+ with the given ``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.
+ For example ``replace("os.path.abspath", value)`` will
+ trigger an ``import os.path`` and a subsequent
+ setattr(os.path, "abspath", value). Or to prevent
+ the requests library from performing requests you can call
+ ``replace("requests.sessions.Session.request", None)``
+ which will lead to an import of ``requests.sessions`` and a call
+ to ``setattr(requests.sessions.Session, "request", None)``.
"""
- if isinstance(target, str):
- name, obj = derive_from_string(target)
- else:
- name, obj = derive_obj_and_name(target)
- return self.setattr(obj, name, value)
+ if not isinstance(import_path, str) or "." not in import_path:
+ raise TypeError("must be absolute import path string, not %r" %
+ (import_path,))
+ rest = []
+ target = import_path
+ while target:
+ try:
+ obj = __import__(target, None, None, "__doc__")
+ except ImportError:
+ if "." not in target:
+ __tracebackhide__ = True
+ pytest.fail("could not import any sub part: %s" %
+ import_path)
+ target, name = target.rsplit(".", 1)
+ rest.append(name)
+ else:
+ assert rest
+ try:
+ while len(rest) > 1:
+ attr = rest.pop()
+ obj = getattr(obj, attr)
+ attr = rest[0]
+ getattr(obj, attr)
+ except AttributeError:
+ __tracebackhide__ = True
+ pytest.fail("object %r has no attribute %r" % (obj, attr))
+ return self.setattr(obj, attr, value)
def setattr(self, obj, name, value, raising=True):
""" set attribute ``name`` on ``obj`` to ``value``, by default
diff -r b4cd6235587f2259678b618c1c5a429babe2a2c5 -r
7f5dba9095f4d77f06c46f19b4c7f4026e6182bd doc/en/monkeypatch.txt
--- a/doc/en/monkeypatch.txt
+++ b/doc/en/monkeypatch.txt
@@ -29,7 +29,7 @@
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'
@@ -37,6 +37,23 @@
then calls into an function that calls it. After the test function
finishes the ``os.path.expanduser`` modification will be undone.
+example: preventing "requests" from remote operations
+------------------------------------------------------
+
+If you want to prevent the "requests" library from performing http
+requests in all your tests, you can do::
+
+ # content of conftest.py
+
+ import pytest
+ @pytest.fixture(autouse=True)
+ def no_requests(monkeypatch):
+ monkeypatch.replace("requests.session.Session.request", None)
+
+This autouse fixture will be executed for all test functions and it
+will replace the method ``request.session.Session.request`` with the
+value None so that any attempts to create http requests will fail.
+
Method reference of the monkeypatch function argument
-----------------------------------------------------
diff -r b4cd6235587f2259678b618c1c5a429babe2a2c5 -r
7f5dba9095f4d77f06c46f19b4c7f4026e6182bd testing/test_monkeypatch.py
--- a/testing/test_monkeypatch.py
+++ b/testing/test_monkeypatch.py
@@ -35,29 +35,7 @@
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"
-
+class TestReplace:
def test_string_expression(self, monkeypatch):
monkeypatch.replace("os.path.abspath", lambda x: "hello2")
assert os.path.abspath("123") == "hello2"
@@ -67,6 +45,17 @@
import _pytest
assert _pytest.config.Config == 42
+ def test_wrong_target(self, monkeypatch):
+ pytest.raises(TypeError, lambda: monkeypatch.replace(None, None))
+
+ def test_unknown_import(self, monkeypatch):
+ pytest.raises(pytest.fail.Exception,
+ lambda: monkeypatch.replace("unkn123.classx", None))
+
+ def test_unknown_attr(self, monkeypatch):
+ pytest.raises(pytest.fail.Exception,
+ lambda: monkeypatch.replace("os.path.qweqwe", None))
+
def test_delattr():
class A:
x = 1
Repository URL: https://bitbucket.org/hpk42/pytest/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
_______________________________________________
pytest-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pytest-commit