Pytest: Better error message needed for parametrized fixtures with unittest

Created on 27 Jun 2017  路  9Comments  路  Source: pytest-dev/pytest

  • [x] Include a detailed description of the bug or suggestion

unittest style tests are allowed to use pytest fixtures through @pytest.mark.usefixtures().
However, if the fixture is paramterized, it crashes and burns and gives a bizarre message of:
E Failed: The requested fixture has no parameter defined for the current test.

That wouldn't be so terribly bad, if it wasn't for the other 96 lines of error message that come with it.

  • [x] pip list of the virtual environment you are using
    pip (9.0.1)
    py (1.4.34)
    pytest (3.1.2)
    setuptools (36.0.1)
    wheel (0.29.0)

  • [x] pytest and operating system versions
    platform linux -- Python 3.6.1, pytest-3.1.2, py-1.4.34, pluggy-0.4.0

pytest 3.1.2, on Ubuntu Linux running on Windows 10.

  • [x] Minimal example if possible

test_one() will pass, test_two() will not only Error, it spews lots of rubbish to confuse the user.

import pytest
import unittest

@pytest.fixture()
def one():
  return 42

@pytest.mark.usefixtures('one')
class TestSomething(unittest.TestCase):
  def test_one(self):
    pass

@pytest.fixture(params=[1,2])
def two(request):
  return request.param

@pytest.mark.usefixtures('two')
class TestSomethingElse(unittest.TestCase):
  def test_two(self):
    pass

output

(unit_venv) okken@RSA22473:~/projects/unit/fail$ pytest test_something.py
================================== test session starts ==================================
platform linux -- Python 3.6.1, pytest-3.1.2, py-1.4.34, pluggy-0.4.0
rootdir: /home/okken/projects/unit/fail, inifile: pytest.ini
collected 2 items

test_something.py .E

======================================== ERRORS =========================================
_____________________ ERROR at setup of TestSomethingElse.test_two ______________________

self = <FixtureRequest for <TestCaseFunction 'test_two'>>
fixturedef = <FixtureDef name='two' scope='function' baseid='test_something.py' >

    def _getfixturevalue(self, fixturedef):
        # prepare a subrequest object before calling fixture function
        # (latter managed by fixturedef)
        argname = fixturedef.argname
        funcitem = self._pyfuncitem
        scope = fixturedef.scope
        try:
>           param = funcitem.callspec.getparam(argname)
E           AttributeError: 'TestCaseFunction' object has no attribute 'callspec'

../unit_venv/lib/python3.6/site-packages/_pytest/fixtures.py:472: AttributeError

During handling of the above exception, another exception occurred:

self = <CallInfo when='setup' exception: The requested fixture has no parameter defined for the current test.

Requested fixt...mething.py:14

Requested here:
/home/okken/projects/unit/unit_venv/lib/python3.6/site-packages/_pytest/fixtures.py:382>
func = <function call_runtest_hook.<locals>.<lambda> at 0x7f7689467f28>, when = 'setup'

    def __init__(self, func, when):
        #: context of invocation: one of "setup", "call",
        #: "teardown", "memocollect"
        self.when = when
        self.start = time()
        try:
>           self.result = func()

../unit_venv/lib/python3.6/site-packages/_pytest/runner.py:157:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../unit_venv/lib/python3.6/site-packages/_pytest/runner.py:145: in <lambda>
    return CallInfo(lambda: ihook(item=item, **kwds), when=when)
../unit_venv/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py:745: in __call__
    return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
../unit_venv/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py:339: in _hookexec
    return self._inner_hookexec(hook, methods, kwargs)
../unit_venv/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py:334: in <lambda>
    _MultiCall(methods, kwargs, hook.spec_opts).execute()
../unit_venv/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py:613: in execute
    return _wrapped_call(hook_impl.function(*args), self.execute)
../unit_venv/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py:254: in _wrapped_call
    return call_outcome.get_result()
../unit_venv/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py:279: in get_result
    raise ex[1].with_traceback(ex[2])
../unit_venv/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py:265: in __init__
    self.result = func()
../unit_venv/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py:614: in execute
    res = hook_impl.function(*args)
../unit_venv/lib/python3.6/site-packages/_pytest/runner.py:94: in pytest_runtest_setup
    item.session._setupstate.prepare(item)
../unit_venv/lib/python3.6/site-packages/_pytest/runner.py:449: in prepare
    col.setup()
../unit_venv/lib/python3.6/site-packages/_pytest/unittest.py:79: in setup
    self._request._fillfixtures()
../unit_venv/lib/python3.6/site-packages/_pytest/fixtures.py:382: in _fillfixtures
    item.funcargs[argname] = self.getfixturevalue(argname)
../unit_venv/lib/python3.6/site-packages/_pytest/fixtures.py:424: in getfixturevalue
    return self._get_active_fixturedef(argname).cached_result[0]
../unit_venv/lib/python3.6/site-packages/_pytest/fixtures.py:449: in _get_active_fixturedef
    result = self._getfixturevalue(fixturedef)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <FixtureRequest for <TestCaseFunction 'test_two'>>
fixturedef = <FixtureDef name='two' scope='function' baseid='test_something.py' >

    def _getfixturevalue(self, fixturedef):
        # prepare a subrequest object before calling fixture function
        # (latter managed by fixturedef)
        argname = fixturedef.argname
        funcitem = self._pyfuncitem
        scope = fixturedef.scope
        try:
            param = funcitem.callspec.getparam(argname)
        except (AttributeError, ValueError):
            param = NOTSET
            param_index = 0
            if fixturedef.params is not None:
                frame = inspect.stack()[3]
                frameinfo = inspect.getframeinfo(frame[0])
                source_path = frameinfo.filename
                source_lineno = frameinfo.lineno
                source_path = py.path.local(source_path)
                if source_path.relto(funcitem.config.rootdir):
                    source_path = source_path.relto(funcitem.config.rootdir)
                msg = (
                    "The requested fixture has no parameter defined for the "
                    "current test.\n\nRequested fixture '{0}' defined in:\n{1}"
                    "\n\nRequested here:\n{2}:{3}".format(
                        fixturedef.argname,
                        getlocation(fixturedef.func, funcitem.config.rootdir),
                        source_path,
                        source_lineno,
                    )
                )
>               fail(msg)
E               Failed: The requested fixture has no parameter defined for the current test.
E
E               Requested fixture 'two' defined in:
E               test_something.py:14
E
E               Requested here:
E               /home/okken/projects/unit/unit_venv/lib/python3.6/site-packages/_pytest/fixtures.py:382

../unit_venv/lib/python3.6/site-packages/_pytest/fixtures.py:494: Failed
=========================== 1 passed, 1 error in 0.58 seconds ===========================
parametrize reporting

Most helpful comment

Since autouse doesn't work with unittest and @parameterized, should this issue be considered a bug? (That would suggest this issue should be renamed as well).

I'm searching for a way to do parameterized tests in unittest class-based tests, and I can't see any supported way of doing this -- this is a significant issue for me, since in general Django tests must still use a TestCase subclass (for DB caching reasons discussed in https://github.com/pytest-dev/pytest-django/issues/514).

If there's a cunning way to get round this, I'd love to know it.

All 9 comments

Thanks @okken!

Is there any way to get around this and use a parameterized pytest fixture with a unittest test class?

@codeguru42 yes, you can inject fixtures using this technique. If you only need parametrized fixtures it might be enough.

@nicoddemus I am already using the autouse flag. The only other difference between the example in that link and what I am doing is that my fixture is a global function rather than a member of the TestCase subclass. Even when I put my fixture inside the class, I get the same "Failed: The requested fixture has no parameter defined for the current test." error.

I have posted a question on StackOverflow. If you would like me to post more details here or in a new issue instead, I will be happy to do so.

I rewrote the entire module in pytest and removed all references to unittest. This seems like the best solution for my particular situation. Hopefully I will be able to refactor the entire test suite to use pure pytest.

@codeguru42 yeah apparently the autouse trick does not work with parametrized fixtures. 馃槥

I rewrote the entire module in pytest and removed all references to unittest.

A quick note: if you need that parametrized fixture for a few tests, you might get away with moving those test methods into pytest-style test methods:

import unittest
import pytest

class Foo(unittest.TestCase):
    def setUp(self):
        print('unittest setUp()')

    def test(self):
        print('normal unittest')

class Test:  # pytest-style
    @pytest.fixture(autouse=True, params=['foo', 'bar'])
    def foo(self, request):
        print('fixture')
        print(request.param)

    def test(self, foo):  
        pass

Since autouse doesn't work with unittest and @parameterized, should this issue be considered a bug? (That would suggest this issue should be renamed as well).

I'm searching for a way to do parameterized tests in unittest class-based tests, and I can't see any supported way of doing this -- this is a significant issue for me, since in general Django tests must still use a TestCase subclass (for DB caching reasons discussed in https://github.com/pytest-dev/pytest-django/issues/514).

If there's a cunning way to get round this, I'd love to know it.

Since autouse doesn't work with unittest and @parameterized, should this issue be considered a bug?

You mean that autouse fixtures cannot be parametrized in unittest? I think this warrants investigation if it is possible to implement it, but I believe it is a separate issue than the one here.

@paultiplady would you please open a separate issue? Thanks!

Was this page helpful?
0 / 5 - 0 ratings