'How to share global variables between tests?

I have a global variable in conftest.py and use it in tests. For example:

conftest.py

api_version = 'v25'
api_url = 'http://www.foobar.com/' + api_version

test_foo.py

from conftest import api_url
import requests

@pytest.fixture
def data():
    return requests.request("GET", api_url)

test_bar(data):
    assert data is not None

Now I want to be able to change api_version from cmd for testing other api version. So I modified conftest.py in the following way:

conftest.py

api_url = None

def pytest_addoption(parser):
    parser.addoption("--api_version", action="store", default="v25", help="By default: v25")

@pytest.fixture(autouse=True, scope='session')
def cmd_param(pytestconfig):
    api_version = pytestconfig.getoption("--mobile_api_ver").lower()
    global api_url
    if api_version in ['v24', 'v25', 'v26', 'v27']:
        api_url = 'http://www.foobar.com/' + api_version
    else:
        raise ValueError('Unknown api version: ' + api_version)

But this doesn't work as I expected because all imports execute before fixtures and test_foo import api_url = None before cmd_param fixture redefines this. Then i write get_api_url method and call it from test module:

conftest.py

api_url = None

def pytest_addoption(parser):
    parser.addoption("--api_version", action="store", default="v25", help="By default: v25")

@pytest.fixture(autouse=True, scope='session')
def cmd_param(pytestconfig):
    api_version = pytestconfig.getoption("--mobile_api_ver").lower()
    global api_url
    if api_version in ['v24', 'v25', 'v26', 'v27']:
        api_url = 'http://www.foobar.com/' + api_version
    else:
        raise ValueError('Unknown api version: ' + api_version)

def get_api_url():
    return api_url

But now I was forced to change test_foo.py too:

test_foo.py

from conftest import get_api_url
import requests

@pytest.fixture
def data():

    return requests.request("GET", get_api_url())

test_bar(data):
    assert data is not None

It works, but solution looks awkward. Is there a more elegant way to use custom cmd options without changing test files?



Solution 1:[1]

Note: pytest_namespace is deprecated now

pytest provides a way to use some global variables within the session. These variables can be used by fixtures as well.

These variables are controlled via pytest hooks.

import pytest

def pytest_namespace():
    return {'my_global_variable': 0}

@pytest.fixture
def data():
    pytest.my_global_variable = 100

def test(data):
    print pytest.my_global_variable

Solution 2:[2]

According to docs, pytest_namespace has been removed in version 4.0:

One can use pytest_configure to share global variables.

Example:

import pytest

def pytest_configure():
    pytest.my_symbol = MySymbol()

Solution 3:[3]

I wouldn't mess with global variables. Just define your fixture to return a value and use that fixture in your tests: Similar to what @milo posted but a lot simpler.

Also you had defined --api_version CLI option but accessing --mobile_api_ver option in your fixture. Additionally your test is just checking that a response object is not None which will never be None, so assert statement will always pass even if response is 404 status, see inline comments.

Here is some code that will work:

contents of conftest.py

import pytest


def pytest_addoption(parser):
    parser.addoption("--api_version", action="store", default="v25", help="By default: v25")


@pytest.fixture(scope='session')
def api_url(pytestconfig):
    api_version = pytestconfig.getoption("--api_version").lower()
    if api_version in ['v24', 'v25', 'v26', 'v27']:
        return 'http://www.foobar.com/' + api_version
    else:
        raise ValueError('Unknown api version: ' + api_version)

contents of test_foo.py

import pytest
import requests


@pytest.fixture
def data(api_url):  # probably a good idea to rename your fixture to a api_response or change what fixture returns.
    return requests.get(api_url)


def test_bar(data):
    print(data.text)
    # below you are not testing data, but merely checking that response object is not None
    assert data is not None  # this will always pass

    # you probably want to test status code and response content
    assert data.status_code == 200
    assert data.json()

Run the tests: pytest -vvv --api_version v24 test_foo.py

Solution 4:[4]

The conftest.py file serves as a means of providing fixtures for an entire directory. Fixtures defined in a conftest.py can be used by any test in that package without needing to import them (pytest will automatically discover them).

https://docs.pytest.org/en/6.2.x/fixture.html#conftest-py-sharing-fixtures-across-multiple-files

tests/
__init__.py

conftest.py
    # content of tests/conftest.py
    import pytest

    @pytest.fixture
    def order():
        return []

    @pytest.fixture
    def top(order, innermost):
        order.append("top")

test_top.py
    # content of tests/test_top.py
    import pytest

    @pytest.fixture
    def innermost(order):
        order.append("innermost top")

    def test_order(order, top):
        assert order == ["innermost top", "top"]

subpackage/
    __init__.py

    conftest.py
        # content of tests/subpackage/conftest.py
        import pytest

        @pytest.fixture
        def mid(order):
            order.append("mid subpackage")

    test_subpackage.py
        # content of tests/subpackage/test_subpackage.py
        import pytest

        @pytest.fixture
        def innermost(order, mid):
            order.append("innermost subpackage")

        def test_order(order, top):
            assert order == ["mid subpackage", "innermost subpackage", "top"]

Solution 5:[5]

You can currently use the pytest object directly as stated in the Docs but only as a stopgap measure:

import pytest


def pytest_configure():
    pytest.my_symbol = MySymbol()

But beware if using the pytest_namespace version as it's deprecated:

old version using the namespace:

class MySymbol:
    ...


def pytest_namespace():
    return {"my_symbol": MySymbol()}

Solution 6:[6]

I just try to get it work without complete changing your code. I hope it could give you some idea.

in conftest.py

api_url_by_option = None

def pytest_addoption(parser):
    parser.addoption("--api_version", action="store", default="v25", help="By default: v25")

@pytest.fixture(autouse=True, scope='session')
def cmd_param(pytestconfig):
    api_version = pytestconfig.getoption("--mobile_api_ver").lower()
    global api_url_by_option
    if api_version in ['v24', 'v25', 'v26', 'v27']:
        api_url_by_option = 'http://www.foobar.com/' + api_version
    else:
        raise ValueError('Unknown api version: ' + api_version)

@pytest.fixture:
def api_url():
    return api_url_by_option

in test_foo.py you don't need to import api_url. Please notice that the api_url fixture from conftest.py is used in fixture data

import requests

@pytest.fixture
def data(api_url):
    return requests.request("GET", api_url)

test_bar(data):
    assert data is not None

Solution 7:[7]

What I do in conftest.py:


class StoreStuffHere:
    something_to_start_with = "value"
    somethingnew = None

#if you want an empty class:

class StoreStuffHere:
   pass

What I do in test_sample.py:

from conftest import StoreStuffHere

store_stuff_here = StoreStuffHere

#this will pass
def test_assert_value_stored():
    store_stuff_here.somethingnew = 45
    assert store_stuff_here.something_to_start_with == "value"

#this will pass
def test_assert_fresh_stored_value():
    assert store_stuff_here.somethingnew == 45

This will work for all the tests in the same module. If you're interested in using the same "storage" across test modules, use a dictionary instead or a named tupple instead of the class I used. In order to make sure you don't get missing values errors when certain tests fail, please initialize all known values with None.

Solution 8:[8]

Just wanted to share the pattern that worked best for me that I feel is easy and explicit.

Within conftest.py I have:

import pytest
from types import SimpleNameSpace


# Temporary Storage

pytest.tmp = SimpleNameSpace()

@pytest.fixture(autouse=True, scope="class")
def set_tmp_cls():
    pytest.tmp.cls = SimpleNameSpace()

@pytest.fixture(autouse=True, scope="module")
def set_tmp_mod():
    pytest.tmp.mod = SimpleNameSpace()

and then when utilizing in a test:

import pytest
from pytest import tmp

def test_foo():
    tmp.cls.var = 1

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4 marcelocarmona
Solution 5 Maicon Mauricio
Solution 6 Maicon Mauricio
Solution 7
Solution 8 Teejay Bruno