Jim Stewart
Jim Stewart

Reputation: 17323

Overriding sub-fixtures in pytest

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

Answers (3)

LondonRob
LondonRob

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

laker93
laker93

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

Sanju
Sanju

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

Related Questions