Search code examples
pythonpytestpython-decorators

pytest fixture not found on a simple decorator, only on Python 3


Pytest fails the following testcase with "fixture 'func' not found" on Python 3.7. On Python 2.7, the same code succeeds. In both cases, pytest 4.6.9 is used:

Content of pytest_decorator_issue/test_issue.py:

import functools

def my_decorator(func):

    def wrapper_func(*args, **kwargs):
        # do something on entry
        ret = func(*args, **kwargs)
        # do something on exit
        return ret

    return functools.update_wrapper(wrapper_func, my_decorator)

@my_decorator
def test_bla():
    # perform some test
    pass

Invocation of pytest on Python 3.7:

$ pytest pytest_decorator_issue -vv
=============== ... test session starts ...
platform darwin -- Python 3.7.5, pytest-4.6.9, py-1.8.0, pluggy-0.13.1 -- .../virtualenvs/pywbem37/bin/python
cachedir: .pytest_cache
rootdir: ...
plugins: requests-mock-1.7.0, cov-2.8.1
collected 1 item                                                                                                                                         

pytest_decorator_issue/test_issue.py::test_bla ERROR                                                                                   [100%]

============ ...  ERRORS ...
____________ ...  ERROR at setup of test_bla ...
file .../pytest_decorator_issue/test_fixture_issue.py, line 3
  def my_decorator(func):
E       fixture 'func' not found
>       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, cov, doctest_namespace, monkeypatch, no_cover, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, requests_mock, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
>       use 'pytest --fixtures [testpath]' for help on them.

.../pytest_decorator_issue/test_fixture_issue.py:3
============== ...  1 error in 0.01 seconds =================================================================

Pytest apparently decides to look for a fixture named 'func' when it sees the argument of the decorator function, but why does it not do that on Python 2.7, and how can I have a simple decorator like this on a pytest test function?

Just for comparison of the versions, on Python 2,7, the relevant pytest output is:

platform darwin -- Python 2.7.16, pytest-4.6.9, py-1.8.0, pluggy-0.13.1 -- .../virtualenvs/pywbem27/bin/python
cachedir: .pytest_cache
rootdir: ...
plugins: requests-mock-1.7.0, cov-2.8.1
collected 1 item                                        

Update:

I just found that when using the functools.wraps decorator instead of functools.update_wrapper(), pytest is happy on both Python 2.7 and 3.7:

def my_decorator(func):

    @functools.wraps(func)
    def wrapper_func(*args, **kwargs):
        # do something on entry
        ret = func(*args, **kwargs)
        # do something on exit
        return ret

    return wrapper_func

Can someone explain this?


Solution

  • The second argument in your call to update_wrapper has to be func instead of my_decorator:

    def my_decorator(func):
    
        def wrapper_func(*args, **kwargs):
            # do something on entry
            ret = func(*args, **kwargs)
            # do something on exit
            return ret
    
        return functools.update_wrapper(wrapper_func, func)
    

    Not sure why this works under Python 2, though.