Reputation: 17323
I'm using pytest with some complicated dependency-injected fixtures. I have fixtures that use other fixtures in a long chain. I'd like to be able to alter some fixtures in the middle of the chain for specific tests.
Given these (simplified) fixtures:
@pytest.fixture
def cache():
return Cache()
# Use cache fixture in a new fixture.
@pytest.fixture
def resource(cache):
return Resource(cache=cache, working=True)
# Use resource fixture in a new fixture.
@pytest.fixture
def service(resource):
return Service(resource=resource)
And some tests:
def test_service_when_resource_working(service):
assert service.status == "good"
def test_service_when_resource_broken(service):
assert service.status == "bad"
How can I override the resource
fixture so that it's like this:
@pytest.fixture
def broken_resource(cache):
return Resource(cache=cache, working=False)
...but only for the test_service_when_resource_broken
test case? I can create a broken_service
that uses broken_resource
, but the reality is that the dependency chain is long, and I want to re-use all the fixtures, but selectively change some of them in the middle for selected tests.
I want to do something like this (pseudocode):
@pytest.override_fixture('resource', 'broken_resource')
def test_service_when_resource_broken(service):
# service should have been instantiated with broken_resource instead of resource.
assert service.status == "bad"
Upvotes: 3
Views: 2842
Reputation: 78863
By putting your tests inside test classes you can achieve this relatively cleanly.
class TestGoodService:
@pytest.fixture
def service(self, service):
service.working = False
return service
def test_service_when_resource_broken(self, service):
assert service.status == "bad"
def test_service_when_resource_working(service):
assert service.status == "good"
The service
fixture inside TestGoodService
will find the fixture from the enclosing scope, but test_service_when_resource_broken
will find your overridden service
fixture.
Upvotes: 0
Reputation: 832
Here's a full working example for pytest 7.4.0 on Python 3.10.8. Note that if you have a pytest.ini file, you'll need to add the marker to the markers
section, see here: https://docs.pytest.org/en/latest/example/markers.html#registering-markers
import pytest
class Cache:
pass
class Resource:
def __init__(self, cache: Cache, working: bool):
self.working = working
class Service:
def __init__(self, resource: Resource):
self.resource = resource
self.status = "good" if self.resource.working else "bad"
@pytest.fixture
def cache():
return Cache()
# Use cache fixture in a new fixture.
@pytest.fixture
def resource(request, cache):
working = True
marker = request.node.get_closest_marker("broken")
if marker:
working = False
return Resource(cache=cache, working=working)
# Use resource fixture in a new fixture.
@pytest.fixture
def service(resource):
return Service(resource=resource)
def test_service_when_resource_working(service):
assert service.status == "good"
@pytest.mark.broken
def test_service_when_resource_broken(service):
assert service.status == "bad"
Upvotes: 2
Reputation: 2194
You can use markers on your tests to achieve what you are expecting. Basically, you mark the test for which you need a different behaviour. In the fixture method look for that marker from the requesting test context and process.
Here is how you can do it.
@pytest.fixture
def cache():
return Cache()
# Use cache fixture in a new fixture.
@pytest.fixture
def resource(request, cache):
working = True
marker = request.node.get_marker("broken")
if marker:
working = False
return Resource(cache=cache, working=working)
# Use resource fixture in a new fixture.
@pytest.fixture
def service(resource):
return Service(resource=resource)
def test_service_when_resource_working(service):
assert service.status == "good"
@pytest.mark.broken
def test_service_when_resource_broken(service):
assert service.status == "bad"
Upvotes: 7