diff --git a/changelog/4545.removal.rst b/changelog/4545.removal.rst new file mode 100644 index 000000000..865d2fc12 --- /dev/null +++ b/changelog/4545.removal.rst @@ -0,0 +1,3 @@ +Calling fixtures directly is now always an error instead of a warning. + +See our `docs `__ on information on how to update your code. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 8cc259cb1..b37be1b7f 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -72,46 +72,6 @@ Becomes: - - -Calling fixtures directly -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 3.7 - -Calling a fixture function directly, as opposed to request them in a test function, is deprecated. - -For example: - -.. code-block:: python - - @pytest.fixture - def cell(): - return ... - - - @pytest.fixture - def full_cell(): - cell = cell() - cell.make_full() - return cell - -This is a great source of confusion to new users, which will often call the fixture functions and request them from test functions interchangeably, which breaks the fixture resolution model. - -In those cases just request the function directly in the dependent fixture: - -.. code-block:: python - - @pytest.fixture - def cell(): - return ... - - - @pytest.fixture - def full_cell(cell): - cell.make_full() - return cell - ``Node.get_marker`` ~~~~~~~~~~~~~~~~~~~ @@ -353,6 +313,58 @@ By passing a string, users expect that pytest will interpret that command-line u on (for example ``bash`` or ``Powershell``), but this is very hard/impossible to do in a portable way. +Calling fixtures directly +~~~~~~~~~~~~~~~~~~~~~~~~~ + +*Removed in version 4.0.* + +Calling a fixture function directly, as opposed to request them in a test function, is deprecated. + +For example: + +.. code-block:: python + + @pytest.fixture + def cell(): + return ... + + + @pytest.fixture + def full_cell(): + cell = cell() + cell.make_full() + return cell + +This is a great source of confusion to new users, which will often call the fixture functions and request them from test functions interchangeably, which breaks the fixture resolution model. + +In those cases just request the function directly in the dependent fixture: + +.. code-block:: python + + @pytest.fixture + def cell(): + return ... + + + @pytest.fixture + def full_cell(cell): + cell.make_full() + return cell + +Alternatively if the fixture function is called multiple times inside a test (making it hard to apply the above pattern) or +if you would like to make minimal changes to the code, you can create a fixture which calls the original function together +with the ``name`` parameter: + +.. code-block:: python + + def cell(): + return ... + + + @pytest.fixture(name="cell") + def cell_fixture(): + return cell() + ``yield`` tests ~~~~~~~~~~~~~~~ diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 30020a9ab..af1b5c792 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -20,11 +20,11 @@ from _pytest.warning_types import UnformattedWarning YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored" -FIXTURE_FUNCTION_CALL = UnformattedWarning( - RemovedInPytest4Warning, - 'Fixture "{name}" called directly. Fixtures are not meant to be called directly, ' - "are created automatically when test functions request them as parameters. " - "See https://docs.pytest.org/en/latest/fixture.html for more information.", +FIXTURE_FUNCTION_CALL = ( + 'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n' + "but are created automatically when test functions request them as parameters.\n" + "See https://docs.pytest.org/en/latest/fixture.html for more information about fixtures, and\n" + "https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly about how to update your code." ) FIXTURE_NAMED_REQUEST = PytestDeprecationWarning( diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 0136dea09..1da7a6c48 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -942,34 +942,17 @@ def _ensure_immutable_ids(ids): return tuple(ids) -def wrap_function_to_warning_if_called_directly(function, fixture_marker): - """Wrap the given fixture function so we can issue warnings about it being called directly, instead of - used as an argument in a test function. +def wrap_function_to_error_out_if_called_directly(function, fixture_marker): + """Wrap the given fixture function so we can raise an error about it being called directly, + instead of used as an argument in a test function. """ - is_yield_function = is_generator(function) - warning = FIXTURE_FUNCTION_CALL.format( + message = FIXTURE_FUNCTION_CALL.format( name=fixture_marker.name or function.__name__ ) - if is_yield_function: - - @functools.wraps(function) - def result(*args, **kwargs): - __tracebackhide__ = True - warnings.warn(warning, stacklevel=3) - for x in function(*args, **kwargs): - yield x - - else: - - @functools.wraps(function) - def result(*args, **kwargs): - __tracebackhide__ = True - warnings.warn(warning, stacklevel=3) - return function(*args, **kwargs) - - if six.PY2: - result.__wrapped__ = function + @six.wraps(function) + def result(*args, **kwargs): + fail(message, pytrace=False) # keep reference to the original function in our own custom attribute so we don't unwrap # further than this point and lose useful wrappings like @mock.patch (#3774) @@ -995,7 +978,7 @@ class FixtureFunctionMarker(object): "fixture is being applied more than once to the same function" ) - function = wrap_function_to_warning_if_called_directly(function, self) + function = wrap_function_to_error_out_if_called_directly(function, self) name = self.name or function.__name__ if name == "request": diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 56828f7c0..d392ac7d9 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -223,17 +223,6 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_false_positives( assert msg not in res.stdout.str() -def test_call_fixture_function_deprecated(): - """Check if a warning is raised if a fixture function is called directly (#3661)""" - - @pytest.fixture - def fix(): - return 1 - - with pytest.deprecated_call(): - assert fix() == 1 - - def test_fixture_named_request(testdir): testdir.copy_example() result = testdir.runpytest() diff --git a/testing/python/fixture.py b/testing/python/fixture.py index a4ef5af87..b6692ac9b 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -3850,3 +3850,14 @@ class TestScopeOrdering(object): ) reprec = testdir.inline_run() reprec.assertoutcome(passed=2) + + +def test_call_fixture_function_error(): + """Check if an error is raised if a fixture function is called directly (#4545)""" + + @pytest.fixture + def fix(): + return 1 + + with pytest.raises(pytest.fail.Exception): + assert fix() == 1