'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 |