What Is The pytest_configure Hook? (A Simple Guide)
Say you’re working on a large test suite with a bunch of tests, each with its own set of requirements and dependencies, across different environments.
You want to run initialization commands in different environments, set up global variables, maybe slack notifications or ping an external API via a webhook to say your tests have begun.
How do you do this in Pytest? How do you tune Pytest to automatically execute tasks before running your tests?
After all you don’t want to write another script to do this. That’s just more code to maintain and more things to go wrong.
The answer lies in Pytest hooks. Hooks are gateways to customize and extend Pytest’s default behavior.
Think of them as strategically placed entry points or “hooks” within the Pytest execution cycle where you can insert your own code to add or alter functionality.
pytest_configure
is a useful initialization hook that’s called after the command-line options are parsed, allowing you to configure the test environment before tests are executed.
In this comprehensive tutorial, you’ll learn about the pytest_configure
hook and how to configure it to fit your testing needs.
We’ll explore its purpose, how to wield it effectively and witness its transformative impact on your Python testing strategy.
So buckle up, and let’s begin!
What You’ll Learn
By the end of this tutorial, you’ll be able to:
- grasp the concept and purpose of hooks in Pytest.
- dive deep into the functionalities and applications of the
pytest_configure
hook. - use
pytest_configure
to - access shared global variables, command line options and define markers within your test suite. - gain insights into best practices for using
pytest_configure
.
Understanding Pytest Hooks
At their core, Pytest hooks are gateway points, allowing you to inject logic at specific stages of the test process to modify or extend the behaviour.
It’s like having the power to pause the execution of your tests, do some wizardry, and then resume, with everything now aligned just the way you want it.
Pytest offers a variety of hooks, each designed for different stages of the test cycle, like bootstrap hooks, initialization hooks and so on.
pytest_configure
sets the stage before your tests run.
It’s your code’s first interaction with the testing environment, where you can add markers, configure plugins, or set command line global options.
Hooks also integrate natively with Pytest Fixtures and Parameters, allowing you to create a testing workflow that’s unique to your project.
Curious? Let’s dive deeper into the pytest_configure
hook.
Project Set Up
Getting Started
Our project has the following structure - a simple repo with a few test and configuration files.
Hooks are generally defined in conftest.py
files, which are automatically discovered by Pytest.
If you’re not familiar with conftest.py
files, check out this guide.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20.
├── .gitignore
├── README.md
├── requirements.txt
└── tests
├── conftest.py
├── example1
│ ├── conftest.py
│ └── test_pytest_configure_markers.py
├── example2
│ ├── conftest.py
│ └── test_pytest_configure_global_var.py
├── example3
│ ├── conftest.py
│ └── test_pytest_configure_cmd.py
├── input.txt
├── password_manager
│ ├── conftest.py
│ └── test_pytest_configure_password.py
└── test_pytest_configure.py
Prerequisites
To achieve the above objectives, knowledge of the following is recommended:
- Python (3.10+)
- Pytest
To get started, clone the repo here and install any dependencies via pip or your favourite package manager.
Using pytest_configure
The pytest_configure
hook takes a parameter config, a pytest.Config
object representing the configuration information.
Its a central point useful for configuring the Pytest environment before the test execution begins.
pytest_configure
doesn’t return anything, but you can use the config
object to access and modify the Pytest configuration.
Define Custom Markers with pytest_configure
Custom markers allow you to tag tests with specific labels (slow, fast, API, UI, etc.), which is useful for categorizing and selectively running tests in a large test suite with hundreds of tests.
To speed up your large test suite you can check out this post with 13 proven ways to improve the runtime of your test suite.
So how and where do you define these markers? (you can use them directly in your test functions, but without defining them, you’ll get warnings from Pytest).
Well, there are a few ways - you can define them in your pytest.ini
file for one.
Another interesting way is using the pytest_configure
hook defined in conftest.py
file.
tests/example1/conftest.py
1
2
3def pytest_configure(config):
config.addinivalue_line("markers", "ui: mark test as a UI test")
config.addinivalue_line("markers", "api: mark test as an API test")
tests/example1/test_pytest_configure_markers.py
1
2
3
4
5
6
7
8
9
10
11
12import pytest
# Define the marker for the test
def test_ui_component():
# code for testing UI component
print("Testing UI component")
# Define the marker for the test
def test_api_call():
# code for testing API call
print("Testing API call")
Here, test_ui_component
is marked as a UI test, and test_api_call
as an API test.
To run only the UI tests, you can use the -m
flag and specify the marker name.1
pytest -m ui -v
Similarly, you can run only the API tests by specifying the marker name.1
pytest -m api -v
Share Global Variables with pytest_configure
Sometimes, you might want to define global variables that are accessible across your test suite. These could be config like settings, environment variables, or even a database connection.
pytest_configure
allows you to add these variables to the config object.
Here’s an example:
tests/example2/conftest.py
1
2def pytest_configure(config):
config.my_global_data = "Shared Value"
In this snippet, we set a global variable my_global_data
with the value Shared Value
.
This variable is no longer confined to a single test or module; it has been given a global stage, accessible through the config
object.
Accessing the global variable is easy. You can use the request
fixture to access the config
object.
tests/example2/test_pytest_configure_global_var.py
1
2
3
4
5
6
7import pytest
def test_example(request):
# Retrieve the global variable from the pytest configuration
global_value = request.config.my_global_variable
print(f"The global variable is: {global_value}")
Accessing Command Line Options via pytest_addoption
To truly harness the power of pytest_configure
, you can pair it with the pytest_addoption hook.
The pytest_addoption
hook allows you to add custom command-line options to pytest.
This duo works like magic, allowing you to define custom command-line options and then use them within your hook.
I’ll show a small example to explain:
tests/example3/conftest.py
1
2
3
4
5def pytest_addoption(parser):
parser.addoption("--custom-option", action="store", default="default")
def pytest_configure(config):
config.custom_option = config.getoption("--custom-option")
The parser.addoption
method defines a new command-line option. To learn more about how command line options can be passed and accessed in Pytest, check out this guide.
In the pytest_configure
function, we have passed the command-line argument --custom-option myValue
.
tests/example3/test_pytest_configure_cmd.py
1
2
3
4import pytest
def test_custom_option(pytestconfig):
assert pytestconfig.custom_option == "myValue" # or any other value passed via command line
The custom option can be easily accessed via the pytestconfig
fixture.1
pytest --custom-option=myValue
Pretty neat, right?
With this basic understanding, let’s dive into detail how to implement pytest_configure
in your projects.
Define Centralized Config via the pytest_configure
hook
As you’ve seen above, pytest_configure
is a powerful hook that allows you to configure the Pytest environment before the test execution begins.
A very interesting use case is to define central configuration settings for your test suite.
Let’s say we want to read a config file into an object and pass it to all our tests.
We can use pytest_configure
to read the file and store its contents in the config
object.
The content can then be extended to all tests via fixtures.
tests/conftest.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import pytest
import os
def pytest_configure(config):
# Get the path to the text file in the same directory as conftest.py
current_directory = os.path.dirname(os.path.abspath(__file__))
input_file_path = os.path.join(current_directory, "input.txt")
# Read the content of the file and store it in the pytest config object
with open(input_file_path, "r") as file:
file_content = file.read()
config._input_file_content = file_content # Store the file content in the pytest config object
def input_file_content(request):
return request.config._input_file_content
The above code snippet includes the read file settings defined in the pytest_configure
hook.
It also defines a fixture input_file_content
that returns the content of the file we read in pytest_configure
.
tests/test_pytest_configure.py
1
2
3
4def test_display_input(input_file_content):
print("Content from input file:")
print(input_file_content)
assert "Pytest with Eric" in input_file_content
The output will look like this:
pytest_configure
- A Replacement For Fixtures?
Now, you might think that why should you use the pytest_configure
hook and why not fixtures themselves?
The answer is simple.
Hooks are NOT a replacement for fixtures. Rather a complement to them.
pytest_configure: Best for global, session-wide settings or variables that remain constant. Useful for setting up global variables, custom markers, custom configuration, and so on.
Fixtures: Best for setting up and tearing down resources needed for specific tests, with great flexibility in scope and reusability. Useful for database connections, API clients and so on.
Practical Example - pytest_configure
Let’s look at another simple use case for the pytest_configure
hook.
After all, in this website we go beyond the basics and look at real world examples.
You’re developing a password manager application and you have some password validation rules in place. Annoying! I know, but it’s for the greater good.
When a user enters a password, there’s an auto-check that ensures the entered password meets your password criteria.
Besides that, you also want to mark your tests with a password
marker, so you can run them separately from the rest of your tests.
To use the pytest_configure
hook, you can define the password requirements in the conftest.py
file.
tests/password_manager/conftest.py
1
2
3
4
5
6
7
8
9
10import re
import pytest
def pytest_configure(config):
config.addinivalue_line(
"markers",
"password: mark a test to check password formatting"
)
config.password_pattern = re.compile(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&-])[A-Za-z\d@$!%*?&-]{8,}$')
As you can see, this conftest.py
defines a regular expression to check if a password has at least one lowercase letter, one uppercase letter, one digit, and one special character. The password length is also set to a minimum of 8 characters.
Now, let’s put this to the test!
tests/password_manager/test_pytest_configure_password.py
1
2
3
4
5
6
7import pytest
def test_password_format(pytestconfig):
password = input("Enter a password for testing: ")
assert pytestconfig.password_pattern.match(password), "Password does not meet the required format"
In the test file, we have a test function, test_password_format
, that prompts the user to enter a password and then checks if it matches the pattern defined in pytest_configure
.
Viola! The password matches our set criteria.
Let’s also look at a failing case.
Albeit a simple example, it shows how you can use pytest_configure
to define global variables and markers to smoothly use in your tests.
Other Useful Pytest Hooks
While pytest_configure
is a powerful and versatile tool in your arsenal, it’s just the beginning.
Beyond pytest_configure
, there are numerous hooks, each with its specific purpose and versatility:
pytest_addoption: This hook is like a custom command center, allowing you to add your own command-line options to Pytest.
pytest_fixture_setup: This hook lets you set up and tear down conditions before and after your fixtures are called, providing a clean and controlled testing canvas.
pytest_generate_tests: This hook is a master of variation, enabling parameterization of tests at runtime.
You can read more about the different hooks in the official Pytest documentation.
Conclusion
That’s all I have for you in this one!
I like to call pytest_configure
and Pytest hooks - your Swiss Army knife. And in this article, you have learned all the reasons why that is so true.
You delved deep into Pytest hooks and pytest_configure
, appreciating its versatility in setting up global variables and environment-specific settings.
You learnt different ways to use pytest_configure
to define custom markers, add command-line options, custom config and so much more.
Lastly, you also compared fixtures with hooks and learned how to use them in tandem to create a testing workflow that’s unique to your project.
From now on, I enocurage you to be curious and experiment with different hooks and configurations to see what works best for your projects.
If you have ideas for improvement or like me to cover anything specific, please send me a message via Twitter, GitHub or Email.
Till the next time… Cheers!
Additional Learning
Link to Example Code Repo
Official Docs - Pytest Configuration
Official Docs - Pytest Hooks
13 Proven Ways To Improve Test Runtime With Pytest
A Beginner’s Guide To pytest_generate_tests
(Explained With 2 Examples)
How To Use Pytest With Command Line Options (Easy To Follow Guide)
How Pytest Fixtures Can Help You Write More Readable And Efficient Tests
How to use pytest hook in global scope
How To Run A Single Test In Pytest (Using CLI And Markers)
What is Setup and Teardown in Pytest? (Importance of a Clean Test Environment)
Global Variable in conftest.py file
Ultimate Guide To Pytest Markers And Good Test Management