How can I pass a parameter or value from a test case to a fixture?

112 Views Asked by At

I have a fixture pretest() like the example below which is intended to be called before every test case. I understand that this fixture is run before running the test case code.

Is there any way to get a value from the test case function so that I can use it inside the fixture?

For example, I need to read json_name defined in the test case and print it inside the pretest fixture before running the test case.

@pytest.fixture(autouse=True)
def pretest(request):
    tc_name = request.node.name
    json_name =    # ==> how can i get this parameter or value from testcase and use here ?
    yield
    
    
def test_case_EVA_01():
    json_name = file1.json


def test_case_EVA_02():
    json_name = file2.json
2

There are 2 best solutions below

3
Gino Mempin On

You seem to be collecting info about each test case to make a test report.

You can use pytest's record_property fixture to store arbitrary information from each test case, which then becomes available as part of the test's user_properties:

from typing import Callable

import pytest
from pytest import FixtureRequest

@pytest.fixture(autouse=True)
def pretest(request: FixtureRequest):
    tc_name = request.node.name

    yield

    # list[tuple[str, object]] => dict[str, object]
    tc_props = dict(request.node.user_properties)
    json_name = tc_props["json_name"]

    # to keep things simple, let's just print it out for now
    print(tc_name, json_name)

def test_case_EVA_01(record_property: Callable[[str, object], None]):
    json_name = "file1.json"
    
    # ... do actual tests ...

    # store test case info
    record_property("json_name", json_name)


def test_case_EVA_02(record_property: Callable[[str, object], None]):
    json_name = "file2.json"

    # ... do actual tests ...

    # store test case info
    record_property("json_name", json_name)

First, modify the fixture to make it a yield fixture, so that control first starts with the fixture, then passes it to the test function, and then finally back to the fixture. Next, in each test function, call the record_property fixture to store the json_name value using "json_name" as its key. Finally, when control comes back to the fixture (i.e. after yield), you can now access the json_name value from user_properties, which is a list of tuples where each tuple is the same key-value you passed to record_property.

Running the tests and turning on -rP to output and print's:

$ pytest -v -rP test1.py
...
test1.py::test_case_EVA_01 PASSED                                                     
test1.py::test_case_EVA_02 PASSED                                                     
...
--------------------------------- Captured stdout teardown ----------------------------------
test_case_EVA_01 file1.json
--------------------------------- Captured stdout teardown ----------------------------------
test_case_EVA_02 file2.json

Now, I don't know your exact use case, but if you really are doing this to make a report, I think an autouse fixture isn't the most appropriate solution. Instead, it's much better to use one of pytest's pytest_runtest_protocol hooks, specifically the pytest_runtest_makereport hook when it's called on report.when=="teardown".

There are quite a number of pytest plugins that make use of this hook for generating reports of different formats, but to keep this answer simple, let's just override the hook so that it creates a simple text file listing the test IDs and the JSON names.

If you add this to conftest.py:

import pytest
from pytest import Item, TestReport

@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item: Item):
    output = yield
    report: TestReport = output.get_result()

    if report.when == "teardown":
        json_name: str = ""
        if item.user_properties:
            tc_props = dict(item.user_properties)
            json_name = tc_props["json_name"]
        with open("report.txt", "a") as f:
            f.write(f"{item.nodeid} ({json_name})")
            f.write("\n")

Then run the tests:

$ pytest -v test1.py
...
test1.py::test_case_EVA_01 PASSED
test1.py::test_case_EVA_02 PASSED

The report should contain:

$ cat report.txt 
test1.py::test_case_EVA_01 (file1.json)
test1.py::test_case_EVA_02 (file2.json)
0
Sunil Kumar On

i could solve my problem using pytest.mark.parametrize and passing required data as parameters. i am not sure if there is any other easy way, but below solution worked.

@pytest.fixture(autouse=True)
def pretest(request):
    tc_name = request.node.name
    json_name = request.node.callspec.params['json_name'] # json file name can be accessed 
    # testcode to perform pretest (json file is being used)
    yield

@pytest.mark.parametrize("json_name", ["file1.json"])
def test_case_EVA_01(json_name):
    # testcase code with json file is being used

@pytest.mark.parametrize("json_name", ["file2.json"])
def test_case_EVA_02(json_name):
    # testcase code with json file is being used