Use __wrapped__ attribute for better py.test fixture compatibility

Create issue
Issue #24 new
rob_bN created an issue

When using pytest "test functions can receive fixture objects by naming them as an input argument". This doesn't work well when also using fudge.patch however as py.test thinks the fudged objects that get passed to the decorated test function are fixtures that it doesn't know about.

pytest accommodates mock.py by checking the value of the __wrapped__ attribute on a decorated test function to determine the actual test function and then uses mock's patching attribute to determine how many of the function's args it should ignore[1].

The __wrapped__ attribute was introduced into wraps in python 3.2 mock.py has an example how to backport that to older versions of python[2]

I've included below an example of how I've modified patch on a project of my own to get it work with pytest fixtures

  1. https://bitbucket.org/hpk42/pytest/src/c4f58165e0d46166ef7b0b05c3f15ac07387ac10/_pytest/python.py?at=default#cl-1681

  2. https://code.google.com/p/mock/source/browse/mock.py#79

from fudge import patch as fudge_patch
import functools


def wraps(func):
    def inner(f):
        f = functools.wraps(func)(f)
        original = getattr(func, '__wrapped__', func)
        f.__wrapped__ = original
        return f
    return inner


class patch(fudge_patch):

    def __call__(self, fn):

        @wraps(fn)
        def caller(*args, **kw):
            fakes = self.__enter__()
            if not isinstance(fakes, (tuple, list)):
                fakes = [fakes]
            args += tuple(fakes)
            value = None
            try:
                value = fn(*args, **kw)
            except:
                etype, val, tb = sys.exc_info()
                self.__exit__(etype, val, tb)
                raise etype, val, tb
            else:
                self.__exit__(None, None, None)
            return value

        # py.test uses the length of mock.patchings to determine how many
        # arguments to ignore when performing its dependency injection
        if not hasattr(caller, 'patchings'):
            caller.patchings = []
        caller.patchings.extend([1 for path in self.obj_paths])
        return caller

Comments (2)

  1. Jagath Gunawardana

    Hi, is this solution incorporated into a version of fudge? Fudge is a great solution. I have been using a combination of pytest.monkeypatch, but being able to use fudge.patch would be better.

  2. Log in to comment