Pytest 8.4 fails to discover a test fixture withtin a test class when combined with the usage of freezegun
- [x] a detailed description of the bug or problem you are having
- [ ] output of
pip listfrom the virtual environment you are using - [x] pytest and operating system versions
- [x] minimal example if possible
Environment: pytest 8.4.1 freezegun 1.5.2
Description: Pytest 8.4 fails to discover a test fixture withtin a test class when combined with the usage of freezegun. The below minimal example runs with pytest 8.3.x
Minimal example:
import pytest
from freezegun import freeze_time
@freeze_time()
class TestA:
@pytest.fixture
def ff(self):
return 1
def test_a(self, ff):
assert ff == 1
it seems freezegun never directly supported fixtures and they just worked by accident
the latest release changed the type of fixture definitions from functions to a marker instance
freezeguns utilities to reshape the definition break pytest fixture discovery
we need to add a freezegun related regression test to pytest and add temporary backward compat in discovery
however this eventually needs a upstream fix in freezegun
Same with responses. Looks like latest release will break a lot of tests
import responses
import pytest
@responses.activate
@pytest.fixture
def mock_response():
responses.add(
responses.GET,
'https://api.example.com/data',
json={'key': 'value'},
status=200
)
yield responses
def test_api_call(mock_response):
assert True
$ pytest test_resp.py 1 ↵
===================================== test session starts =====================================
platform linux -- Python 3.11.2, pytest-8.4.0, pluggy-1.5.0
rootdir: /home/gawel/tmp
plugins: Faker-37.1.0, libtmux-0.37.0, django-webtest-1.9.13
collected 1 item
test_resp.py E [100%]
=========================================== ERRORS ============================================
_______________________________ ERROR at setup of test_api_call _______________________________
file /home/gawel/tmp/test_resp.py, line 17
def test_api_call(mock_response):
E fixture 'mock_response' not found
> available fixtures: _session_faker, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, capteesys, clear_env, config_file, django_app, django_app_factory, django_app_mixin, doctest_namespace, faker, home_path, home_user_name, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, server, session, session_params, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, user_path, zshrc
> use 'pytest --fixtures [testpath]' for help on them.
Hi @RonnyPfannschmidt!
the latest release changed the type of fixture definitions from functions to a marker instance
Can you expand a bit more on this? As a library developer, if I want to write a class-decorator that acts on all methods, how would I determine whether a method is a fixture?
I've opened a PR in freezegun where I look for the existence of the _pytestfixturefunction attribute, but I'm not sure whether that's the right way 👀
https://github.com/spulec/freezegun/pull/576/files
We now have instances of fixture definition
They are currently still callable to raise a better error but a future release will remove the method altogether
I took a quick look at the pr
Its not clear to me whether freezegun was ever correct for yield fixtures
This bug just blew up the tests for our AWS python lambda. Looks like moto makes pytest 8.4.0 unhappy as well.
import pytest
import boto3
from moto import mock_aws
@mock_aws
@pytest.fixture
def ses_v1(base_env, mock_ses):
return boto3.client('ses', region_name="us-east-1")
def test_api_call(ses_v1):
assert True
Blows up with the same fixture 'ses_v1' not found error.
Seems like we need to throw a error if a fixture gets wrapped
Decorating a defined fixture is always completely wrong and was completely incorrect since years
Sounds like I need to use the context_manager version of mock_aws instead of the decorator, as below. The test_context_manager test passes.
import pytest
import boto3
from moto import mock_aws
@mock_aws
@pytest.fixture
def decorated_fixture():
return boto3.client('ses', region_name="us-east-1")
@pytest.fixture
def context_manager_fixture():
with mock_aws():
return boto3.client('ses', region_name="us-east-1")
def test_decorated(decorated_fixture):
assert True
def test_context_manager(context_manager_fixture):
assert True
Decorating a defined fixture is always completely wrong and was completely incorrect since years
We need to be able to provide library authors to at least be able to write correct code that handles pytest fixtures. This allows them to decide what to do with that information: error out, workaround it, etc.
We should consider making either FixtureFunctionMarker and FixtureFunctionDefinition public API, or provide some other public API to at least allow clients to inspect and verify that an object is a pytest fixture.
Id like to introduce a base class for this purpose that will eventually expand in function
Id like to introduce a base class for this purpose that will eventually expand in function
Could you exemplify?
provide some other public API to at least allow clients to inspect and verify that an object is a pytest fixture.
I'm leaning towards something like the above, using a clear-cut public API and avoid exposing any internals.
Im thinking of introducing fixture definitions that are independent of the definition location support type annotation and possible parallel activation with different parameters
Its still too fuzzy to be concrete
Its still too fuzzy to be concrete
Indeed I don't see a straightforward solution.
To outline the problem:
Prior to 8.4, @fixture returned a function, as is usual for decorators. Applying another decorator on top of it worked as expected.
Since 8.4, @fixture now returns an internal object, FixtureFunctionDefinition.
pytest now collects fixtures by looking for FixtureFunctionDefinition instances in the module namespace, which causes decorated fixtures to not be collected anymore, because the outer decorator returns a function. Even if they were collected, it would also break the decoration eventually, because calling FixtureFunctionDefinition directly raises a warning (and will error out in the future).
Worth noting that, depending on the decorator, changing the order of the decorators might workaround the issue.
For example:
@some_decorator
@pytest.fixture
def foo(): ...
To:
@pytest.fixture
@some_decorator
def foo(): ...
I'll check that with moto. Maybe
@pytest.fixture
@mock_aws
def mocked_ses(): ...
will work.
As a cheap workaround (Python 3.13 syntax):
From:
import pytest
from freezegun import freeze_time
class Foo:
...
@freeze_time("2025-04-01 12:34:56")
class TestFoo:
@pytest.fixture(name="foo")
def fixture_foo(self) -> Foo:
return Foo()
def test_something(self, foo: Foo) -> None:
assert foo
To:
import pytest
from collections.abc import Generator
from freezegun import freeze_time
from freezegun.api import StepTickTimeFactory, TickingDateTimeFactory, FrozenDateTimeFactory
type FreezeTimeFactory = StepTickTimeFactory | TickingDateTimeFactory | FrozenDateTimeFactory
@pytest.fixture(name="frozen_datetime")
def fixture_frozen_datetime() -> Generator[FreezeTimeFactory]:
"""Fixture to freeze the current datetime for testing purposes.
Workaround for https://github.com/pytest-dev/pytest/issues/13479
"""
with freeze_time("2025-04-01 12:34:56") as frozen_time:
yield frozen_time
class Foo:
...
@pytest.mark.usefixtures("frozen_datetime")
class TestFoo:
@pytest.fixture(name="foo")
def fixture_foo(self) -> Foo:
return Foo()
def test_something(self, foo: Foo) -> None:
assert foo
Note that for freezegun specificially, there is pytest-dev/pytest-freezer you might want to use instead of hand-rolling a fixture.
I'll check that with moto. Maybe
@pytest.fixture @mock_aws def mocked_ses(): ...will work.
This fixed it for me, thanks!
Freezegun should be working now https://github.com/spulec/freezegun/issues/575#issuecomment-3065959304