How to pass variable from test function to fixture in pytest? - pytest

Below is the example code from contest.py
#pytest.fixture
def required_input(var1): # not sure if i can pass var1 to the fixture.
return var1+"_test"
below is the test case from test_code.py
def test_case1(required_input):
x = required_input("tt") # Not sure if it can be called like this.
assert 'tt_test' == x
How can I pass variable from my test function to a fixture?

A fixture can be a function:
#pytest.fixture
def required_input(): # not sure if i can pass var1 to the fixture.
return lambda var1: return var1+"_test"

Related

Pytest: Passing on multiple parameters to a fixture

I would like to use a flexible fixture for a data call that can be reused across the test suite. For this purpose I like to ideally pass on more than on parameter. However the code fragment below generally works only for one parameter:
#pytest.fixture
def getData(request):
"""Variable data query"""
data_detail = request.param
In the pytest documentation there is no hint that more than one parameter may work, e.g. such as:
#pytest.fixture
def getData(request):
"""Variable data query"""
data_detail = request.param[0]
time_detail = request.param[1]
Does anyone have a hint how to build a pytest fixture to which several parameters / arguments may be passed on?
You can use a tuple or a dict as the fixture parameter:
#pytest.fixture
def tuple_params(request):
yield sum(request.param)
#pytest.mark.parametrize("tuple_params", [(1, 2, 3)], indirect=True)
def test_tuple_params(tuple_params):
print(tuple_params) # 6
#pytest.fixture
def dict_params(request):
yield f"{request.param['a']}_{request.param['b']}"
#pytest.mark.parametrize("dict_params", [{"a": "foo", "b": "bar"}],
indirect=True)
def test_dict_params(dict_params):
print(dict_params) # foo_bar
Generally, the parameter can be any object, so you can always put your fixture parameters in a suitable object.
With a tuple or a list parameter, you can also access the values per index as in your example.

Using fixtures at collect time in pytest

I use testinfra with ansible transport. It provides host fixture which has ansible, so I can do host.ansible.get_variables().
Now I need to create a parametrization of test based on value from this inventory.
Inventory:
foo:
hosts:
foo1:
somedata:
- data1
- data2
I want to write a test which tests each of 'data' from somedata for each host in inventory. 'Each host' part is handled by testnfra, but I'm struggling with parametrization of the test:
#pytest.fixture
def somedata(host):
return host.ansible.get_variables()["somedata"]
#pytest.fixture(params=somedata):
def data(request):
return request.param
def test_data(host, data):
assert 'data' in data
I've tried both ways:
#pytest.fixture(params=somedata) -> TypeError: 'function' object is not iterable
#pytest.fixture(params=somedata()) -> Fixture "somedata" called directly. Fixtures are not meant to be called directly...
How can I do this? I understand that I can't change the number of tests at test time, but I pretty sure I have the same inventory at collection time, so, theoretically, it can be doable...
After reading a lot of source code I have came to conclusion, that it's impossible to call fixtures at collection time. There are no fixtures at collection time, and any parametrization should happen before any tests are called. Moreover, it's impossible to change number of tests at test time (so no fixture could change that).
Answering my own question on using Ansible inventory to parametrize a test function: It's possible, but it requires manually reading inventory, hosts, etc. There is a special hook for that: pytest_generate_tests (it's a function, not a fixture).
My current code to get any test parametrized by host_interface fixture is:
def cartesian(hosts, ar):
for host in hosts:
for interface in ar.get_variables(host).get("interfaces",[]):
yield (host, interface)
def pytest_generate_tests(metafunc):
if 'host_interface' in metafunc.fixturenames:
inventory_file = metafunc.config.getoption('ansible_inventory')
ansible_config = testinfra.utils.ansible_runner.get_ansible_config()
inventory = testinfra.utils.ansible_runner.get_ansible_inventory(ansible_config, inventory_file)
ar = testinfra.utils.ansible_runner.AnsibleRunner(inventory_file)
hosts = ar.get_hosts(metafunc.config.option.hosts)
metafunc.parametrize("host_interface", cartesian(hosts, ar))
You should use helper function instead of fixture to parametrize another fixture. Fixtures can not be used as decorator parameters in pytest.
def somedata(host):
return host.ansible.get_variables()["somedata"]
#pytest.fixture(params=somedata()):
def data(request):
return request.param
def test_data(host, data):
assert 'data' in data
This assumes that the host is not a fixture.
If the host is a fixture, there is hacky way to get around the problem. You should write the parameters to a tmp file or in a environment variable and read it with a helper function.
import os
#pytest.fixture(autouse=True)
def somedata(host):
os.environ["host_param"] = host.ansible.get_variables()["somedata"]
def get_params():
return os.environ["host_param"] # do some clean up to return a list instead of a string
#pytest.fixture(params=get_params()):
def data(request):
return request.param
def test_data(host, data):
assert 'data' in data

How to pass fixture when parametrizing a test

I am trying to parametrize my test.
In the setup method which returns a list, I am calling a fixture (app_config).
Now, i want to call the setup so that the list can be used as a parameter values inside the test.
The problem i am running into is that i cannot pass app_config fixture when calling setup in the parametrize decorator.
def setup(app_config):
member = app_config.membership
output = app_config.plan_data
ls = list(zip(member, output))
return ls
#pytest.mark.parametrize('member, output', setup(app_config))
def test_concentric(app_config, member, output):
....
....
Is there an elegant way to pass setup method in the parametrize decorator or any other way to approach this?
Unfortunately, starting with pytest version 4, it has become impossible to call fixtures like regular functions.
https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly
https://github.com/pytest-dev/pytest/issues/3950
In your case I can recommend not using fixtures and switch to normal functions.
For example, it might look like this:
import pytest
def app_config():
membership = ['a', 'b', 'c']
plan_data = [1, 2, 3]
return {'membership': membership,
'plan_data': plan_data}
def setup_func(config_func):
data = config_func()
member = data['membership']
output = data['plan_data']
ls = list(zip(member, output))
return ls
#pytest.mark.parametrize('member, output', setup_func(app_config))
def test_concentric(member, output):
print(member, output)
....
NB! Avoid the setup() function/fixture name because it will conflict with pytest.runner's internals.

Passing arguments to instantiate object in Pytest

I have a class which I would like to instantiate using different sets of input parameters, comparing a property on the resultant object to a passed in value.
I am using the indirect flag on #pytest.fixture for the arguments which are sent to the class constructor. I am trying to unpack kwargs in the constructor. Unsuccesfully. This is the error:
TypeError: type object argument after ** must be a mapping, not SubRequest
Code:
import pytest
class MyClass:
def __init__(self, a):
self.a = a
#pytest.fixture
def my_object(request):
yield MyClass(**request)
# first element = arguments to MyClass, second element = value to compare test to
TEST_CASES = [({"a":1}, 1)]
#pytest.mark.parametrize("test, expected", TEST_CASES, indirect=["test"])
def test_1(my_object, test, expected):
assert my_object.a == expected
My goal is to have the object arguments and their test value TEST_CASES in one structure for easy inspection
I've suggest you a working example. Problem was in test code design. The parameter indirect should be True. Indirect parametrization with multiple fixtures should be done as described in docs. And fixture got all params in his request.param attribute.
import pytest
class MyClass:
def __init__(self, a):
self.a = a
#pytest.yield_fixture
def test_case(request):
params, expected = request.param
yield MyClass(**params), expected
# first element = arguments to MyClass, second element = value to compare test to
TEST_CASES = [({"a": 1}, 1)]
#pytest.mark.parametrize("test_case", TEST_CASES, indirect=True)
def test_1(test_case):
my_object, expected = test_case
assert my_object.a == expected

Parametrizing a pytest fixture that returns an array

I have to use an array of fixtures that I want to paramterize. I'll give the simplest example below.
conftest.py
#pytest.fixture
def client():
client = do_some_stuff_to_setup_client()
return client;
test_file.py
#pytest.fixture
def data(client):
ids = client.get_all_ids()
return ids # returns array of numbers [1,2,3,4]
#pytest.mark.parametize('data', (array_of_ids_needed_here), indirect=True)
def test_something(client, data):
client.do_stuff(data)
I want this to run against 1,2,3,4 but with indirect param it returns data = [1,2,3,4] not as 4 different numbers. Any idea how to do this?