danijar
danijar

Reputation: 34185

How to have two issued fixtures depend on each other?

In test_something(), the app instance should be the same as used by the login instance.

@pytest.fixture
def app():
    # ...
    return app

@pytest.fixture
def login(app):
    # ...
    return login

def test_something(self, app, login):
    pass

What I tried is returning both objects from the second fixture, but I wouldn't call this idiomatic.

@pytest.fixture
def app_and_login(app):
    # ...
    return app, login

def test_something(self, app_and_login):
    app, login = login_and_login

Is there a better way to do this?

Upvotes: 4

Views: 2886

Answers (4)

ipmcc
ipmcc

Reputation: 29906

I know this question is old, but I'm not seeing the behavior you describe, and I got really sidetracked by this Q&A until I thought, "It can't work that way..." and tested it out for myself. I tried this on:

platform darwin -- Python 3.7.7, pytest-6.0.1, py-1.9.0, pluggy-0.13.1 -- .../bin/python3

I made this test, and it passes:

import collections
import pytest

AppObject = collections.namedtuple("App", ["name", "login"])

@pytest.fixture
def app():
    app = AppObject("appname", "applogin")
    return app

@pytest.fixture
def login(app):
    return app.login

def test_something(app, login):
    assert isinstance(app, AppObject) and app.name == "appname"
    assert isinstance(login, str) and login == "applogin"

OP seems concerned about the app object being received by the login fixture being different from the app returned by the app fixture. I don't see that happening. For instance, if you add some helpful print statements like this:

import collections
import pytest

AppObject = collections.namedtuple("App", ["name", "login"])

@pytest.fixture
def app():
    app = AppObject("appname", "applogin")
    print("Object id in app fixture: %d" % id(app))
    return app

@pytest.fixture
def login(app):
    print("Object id in login fixture: %d" % id(app))
    return app.login

def test_something(app, login):
    print("Object id in test: %d" % id(app))
    assert isinstance(app, AppObject) and app.name == "appname"
    assert isinstance(login, str) and login == "applogin"

I see this output:

Object id in app fixture: 4451368624
Object id in login fixture: 4451368624
Object id in test: 4451368624

...so it's definitely the same object in all three places. Nested fixtures like this "just work" for me, so I'm either missing the point of the question you were asking, or behavior has changed, or... something else. But I wanted to leave this here for others who come along looking for nested/dependent fixtures like this.

Upvotes: 0

The Compiler
The Compiler

Reputation: 11939

As you described, the fixture already is shared for the runtime of the test by default.

This isn't really documented explicitly anywhere (or at least I haven't found it), but it's somewhat implicit: Sharing a fixture across tests in a module describes the scope parameter, and the default scope is function.

Other scopes would e.g. be module (share/cache the fixture for all tests in the same module) or session (cache the fixture for the whole test session).

Upvotes: 2

Sergei Voronezhskii
Sergei Voronezhskii

Reputation: 2362

You can return it as object, and use third fixtue:

import pytest
from collections import namedtuple

Auth = namedtuple('Auth', 'app, login')

@pytest.fixture
def app():
    return 'APP'

@pytest.fixture
def login(app):
    return 'Login at %s' % app

@pytest.fixture
def auth(app, login):
    return Auth(app, login)

def test_something(auth):
    assert auth.app in auth.login

Upvotes: 0

danijar
danijar

Reputation: 34185

I didn't expect this but it seems to be the default behavior. I couldn't find any documentation about this though. Hints are appreciated.

Upvotes: 0

Related Questions