How To Access Test Details With Pytest Request Fixture

Have you ever wanted your tests to behave differently based on command-line inputs?

Perhaps connect to a different database — depending on whether tests are run locally or in a CI/CD pipeline?

Or maybe you want to extract information about the test execution context in your tests - e.g. test module name, file path, or scope?

The built-in Pytest request fixture is here to help solve these problems.

The request fixture is a powerful feature and can be used to perform not just the above but a whole set of useful Pytest operations.

In this article, we’ll dive into this interesting feature and see how to use it with a practical example. We’ll cover how to use request with Pytest Addoption, addfinalizer, getfixturevalue, and accessing test properties.

Let’s begin.

The Request Fixture

The request fixture provides information about the context of the test function that requests it.

It can be used to control and inspect the current test environment and conditions under which the test is run.

With request, tests can dynamically select resources or configurations, apply conditional markers, or perform specific cleanup actions based on the test outcome or setup requirements.

Moreover, the request fixture helps control test dependencies and their lifecycles, through methods such as getfixturevalue and addfinalizer.

These methods allow for on-demand fixture activation and teardown, critical in managing complex test environments involving external resources like databases or network connections.

Ok, that’s a mouthful. Let’s see how to use the request fixture.

Project Setup

Let’s get started with the coding.

Clone Repo

Clone the repository containing the example code.

We’ll be using Poetry to manage our dependencies and virtual environment.

This is a very simple example with only Pytest as a dependency so feel free to use a package manager of your choice.

Install pyenv

I use pyenv to manage my Python versions but you can use any other tool.

Let’s set the Python version in the terminal to 3.12

1
2
$ pyenv install 3.12   
$ pyenv local 3.12

Install Dependencies

1
2
3
$ poetry env use 3.12.3    
$ poetry env info # Verify
$ poetry install

Let’s now dive into the tests.

Using the request Fixture

Let’s explore what the request fixture does and how to use it.

Here’s a link to the docs where you can explore the various methods and properties of this fixture.

request.config.getoption

Let’s say you want your tests to connect to a database and pass the dburl at runtime.

You can access the argument using Pytest Addoption and request.config.getoption to get the value of this command line argument.

Note — parser.addoption only seems to be detected when defined in conftest.py .

tests/conftest.py

1
2
3
4
5
6
7
def pytest_addoption(parser):  
parser.addoption(
"--dburl", # For Postgres use "postgresql://user:password@localhost/dbname"
action="store",
default="sqlite:///./test_db.db", # Default uses SQLite in memory db
help="Database URL to use for tests.",
)

tests/test_config_getoption.py

1
2
3
4
def test_get_db_url(request):  
db_url = request.config.getoption("--dburl")
print(f"db_url: {db_url}")
assert db_url is not None

We can extend this to work with fixtures.

1
2
3
4
5
6
7
8
@pytest.fixture(scope="session")  
def db_url(request):
"""Fixture to retrieve the database URL."""
return request.config.getoption("--dburl")

def test_get_db_url_fixture(db_url):
print(f"db_url: {db_url}")
assert db_url is not None

Pytest Request Fixture - Config Get Option

Pytest Request Fixture - Config Get Option 2

Here’s an example of how we’ve used it to decide whether to connect to SQLite or Postgres in our test execution, based on the command-line input.

request.addfinalizer

The addfinalizer method allows you to register cleanup functions that are executed after a test has been completed, regardless of whether the test passed or failed.

This is similar to the yield operation that occurs at the end of fixture setup and teardown.

tests/test_addfinalizer.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pytest  


@pytest.fixture
def resource_setup(request):
print("Resource setup")

def resource_teardown():
print("Resource teardown")

request.addfinalizer(resource_teardown)
return "Resource"


def test_resource(resource_setup):
print("Testing with resource")
assert resource_setup == "Resource"

Pytest Request Fixture - Add Finalizer

Note how the fixture was set up, the test was run, and then teardown logic executed.

request.getfixturevalue

This method is especially useful when the decision to use a specific fixture is not straightforward and needs to be made based on conditions that are only known during the test execution.

Let’s see an example.

tests/test_getfixturevalue.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pytest  


@pytest.fixture
def dev_config():
print("Loading Development Configuration")
return {"db": "dev_db", "debug": True}


@pytest.fixture
def prod_config():
print("Loading Production Configuration")
return {"db": "prod_db", "debug": False}

def test_dev_config_loading(dev_config):
# Directly use the dev_config fixture
print(f"Testing with Development Config: {dev_config}")
assert dev_config['debug'] is True
assert dev_config['db'] == 'dev_db'

Pytest Request Fixture - Get Fixture Value

This works, great. How about if we want to write only 1 test and leverage Pytest parametrization?

Unfortunately, there is still a feature request open for using fixtures in parametrization, but we can use request.getfixturevalue in the meantime.

tests/test_getfixturevalue.py

1
2
3
4
5
6
7
8
9
10
11
12
13
@pytest.mark.parametrize("env_name", ["dev_config", "prod_config"])  
def test_config_loading(env_name, request):
# Dynamically get the correct fixture based on the env_name
config = request.getfixturevalue(env_name)
print(f"Testing with: {config}")

# Perform different assertions based on the environment
if env_name == "dev_config":
assert config["debug"] is True
assert config["db"] == "dev_db"
else:
assert config["debug"] is False
assert config["db"] == "prod_db"

Pytest Request Fixture - Get Fixture Value Parametrize

Accessing Test Properties using request

You can also access the test properties using the request fixture.

This is very useful when you want to access properties like

  • Test function name
  • Module name
  • Filepath
  • Class name

tests/test_request_properties.py

1
2
3
4
5
6
7
8
9
10
11
12
13
import pytest  


def test_show_request_properties(request):
print(f"Test Function Name: {request.node.name}")
print(f"Test Module Name: {request.node.module.__name__}")
print(f"Test File Path: {request.node.fspath}")
print(f"Test Directory: {request.node.fspath.dirname}")
print(f"Test File Name: {request.node.fspath.basename}")
print(f"Test Function: {request.node.originalname}")
print(f"Test Class: {request.node.cls}")
print(f"Test Function Scope: {request.scope}")
print(f"Test Function Keywords: {request.keywords}")

Similarly, you can also access the Key-Value pairs when using Pytest Parametrization.

1
2
3
4
5
6
7
8
# Parameterize a test function with different pairs of numbers  
@pytest.mark.parametrize("a,b", [(1, 2), (2, 3), (3, 4)])
def test_addition(request, a, b):
# Accessing the parameters used in this test instance
params = request.node.callspec.params

# Print the parameters and result
print(f"Parameters: {params}")

Pytest Request Fixture - Request Properties

This gives us a dict containing KV pairs for the various inputs of a and b .

The request fixture has a few more methods but these are among the most used.

Conclusion

This was a pretty short article on how to use the built-in Pytest request fixture.

In this article, you learned about the request fixture — the problem it solves and the value it offers.

You can use it to get config values, access fixture values dynamically, get test context information, and even perform teardown operations.

It’s a pretty neat feature of Pytest and can unlock some cool functionality when used correctly, particularly around config and runtime.

When combined with other Pytest features like addoption or sessionstart can be very powerful. So go ahead and try it out for yourself.

If you have ideas for improvement or like me to cover anything specific, please message me via Twitter, GitHub, or Email.

Till the next time… Cheers!

Additional Reading

Example Code Used
Pytest Request Fixture Docs
Pytest API Testing with FastAPI, SQLAlchemy, Postgres 
How To Use Pytest With Command Line Options (Easy To Follow Guide)
What is Setup and Teardown in Pytest? (Importance of a Clean Test Environment)
How to Effortlessly Generate Unit Test Cases with Pytest Parameterized Tests
How to Use Fixtures as Arguments in pytest.mark.parametrize