pytest_sessionstart and pytest_sessionfinish (A Complete Guide)

Imagine kicking off a lengthy test suite, only to find they all failed because a database wasn’t accessible or an API endpoint was down.

Frustrating, right?

Maybe you’ve experienced this scenario before, where a single failure cascades into multiple test failures, wasting time, resources, and increasing costs in your CI/CD pipelines.

But what if you could check critical resources and dependencies before running your tests, to avoid such situations?

How about if you wanted your tests to do something before collection - maybe ping a webhook or send a Slack message?

This is where Pytest’s session start and session finish hooks come to the rescue.

These hooks allow you to pre-check critical resources and dependencies or execute custom code before any tests run, ensuring you catch issues early and avoid a cascade of failures.

In this article, we’ll dive into Pytest’s session management hooks, exploring how they can save you time and headaches by validating your test environment upfront.

We’ll cover what these hooks are, why they’re essential, and how to implement them effectively.

Say goodbye to wasted test runs and hello to more reliable testing processes with pytest_sessionstart and pytest_sessionfinish hooks.

Let’s get started.

Example Code

What You’ll Learn

By the end of this article you’ll be able to:

  • Understand and implement Pytest session management hooks.
  • Verify resources before the test execution begins.
  • Verify and pass custom CLI arguments to tests during session management.

Session Management Hooks in Pytest

Pytest’s session management hooks let you run custom steps before test collection.

This approach is extremely valuable when you need to perform pre-checks on resources and dependencies before the test session begins.

The two powerful session management hooks in Pytest are pytest_sessionstart and pytest_sessionfinish.

These hooks help you manage resources effectively, building a robust, scalable, and maintainable test suite that efficiently handles complex test environments.

  • pytest_sessionstart(session):
    The pytest_sessionstart hook is called after the Session object is created and before test collection and execution begins. This is the perfect moment to perform checks like database connectivity and API availability to ensure everything is in place for your tests to run smoothly.

  • pytest_sessionfinish(session, exitstatus):
    The pytest_sessionfinish hook is called after the test session has completed, right before Pytest returns the exit status. This allows you to clean up resources and handle any final tasks necessary after all tests have been executed, perhaps generate reports or send Slack notifications.

Effective session management is crucial for optimizing test performance and resource utilization, especially in complex test environments. Be aware that a few exceptions can be raised when creating a session object:

  • Interrupted: Indicates an interruption during the session object creation.
  • Failed: Indicates a failure to create the session object.

By leveraging session management hooks, you can ensure your test environment is always in top shape, preventing cascading failures and enhancing test reliability.

Importance of Session Management

Understanding session management in Pytest is crucial for efficient test execution. Here’s why they matter:

  • Enhance Consistency: Creating a consistent environment reduces test flakiness caused by inconsistent setup or teardown operations.

  • Simplify Resource Management: pytest_sessionstart and pytest_sessionfinish centralize setup and teardown logic, managing global resources for the entire test session effectively.

  • Save Time and Money: Pre-check critical resources before the test session to avoid errors from unexpected issues like connection failures or invalid API responses, saving executing time and money.

  • Optimize Resource Utilization: Initialize essential resources like databases or web servers only once per test session, minimizing the overhead of repetitive setup and teardown.

By leveraging these hooks, you ensure a reliable, efficient, and maintainable testing process.

Project Setup

Let’s check out how to use the pytest_sessionstart and pytest_sessionfinish hooks in a practical example.

Prerequisites

  • Python (3.12)
  • Pytest

Getting Started

Clone the repository containing the example code or if you prefer to start from scratch you can do so by following the similar directory structure.

1
2
3
4
5
6
7
8
9
10
├── .gitignore
├── README.md
├── poetry.lock
├── pyproject.toml
├── tests
| ├── conftest.py
│ └── test_leap_year.py
└── src
└── leap_year.py

We’ll be using Poetry to manage our dependencies and virtual environment so if you’re not familiar with Poetry I encourage you to start using it.

It’s a great tool that picks the best from pip, venv, conda, and others.

How to Use Pytest Session Management Hooks

Example Code

Our example features a simple leap year checker that uses the Digidates API. Let’s see how it works:

src/leap_year.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import requests
from requests.exceptions import RequestException


class LeapYear:
"""
Class to manage API calls and check leap years.

Args:
link (str): The API endpoint link.
"""

def __init__(self, endpoint: str):
self.endpoint = endpoint

def check_connection(self) -> int:
"""
Checks the API connection.

Returns:
int: The status code of the API response.
"""
try:
response = requests.get(self.endpoint)
response.raise_for_status() # Raise an HTTPError for bad responses
return response.status_code
except RequestException as e:
print(f"Error checking connection: {e}")
return None

def check_leap_year(self, year: str) -> bool:
"""
Gets the API response data to check leap year.

Returns:
str: The content of the API response.
"""
try:
response = requests.get(f"{self.endpoint}/?year={year}")
response.raise_for_status() # Raise an HTTPError for bad responses
if response.status_code == 200 and response.json()["leapyear"] is True:
return True
elif response.status_code == 200 and response.json()["leapyear"] is False:
return False
else:
return response.json()
except RequestException as e:
print(f"Error fetching data: {e}")
return False

The LeapYear class initializes with an endpoint URL. The class includes the following methods:

  • check_connection: Checks the API connection and returns either the status code or None.
  • check_leap_year: Receives the API response to check if a year is a leap year, returning JSON or a boolean.

Test Code

Here are some simple tests for our example code:

tests/test_leap_year.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pytest
from src.leap_year import LeapYear


@pytest.fixture()
def leap_year():
return LeapYear("https://digidates.de/api/v1/leapyear")


def test_leap_year_1(leap_year):
response = leap_year.check_leap_year(year=2024)
assert response is True


def test_leap_year_2(leap_year):
response = leap_year.check_leap_year(year=2023)
assert response is False

Here, we have a fixture leap_year() that returns an instance of the LeapYear class with an API endpoint URL.

We also have two tests, test_leap_year_1 and test_leap_year_2, each using different years.

In a production setting you’d want various exceptions and failures too.

Add Pytest Sessionstart

To get started, we need to create a conftest.py file to implement the pytest_sessionstart hook.

As we’ve learned, Pytest triggers the pytest_sessionstart hook after the session is created but before test collection and execution begins.

In our case, we’ll use this hook to perform pre-checks, such as verifying the API connection, to ensure everything is set up correctly before running our tests.

Let’s see how we can do that!

tests/conftest.py

1
2
3
4
5
6
7
8
9
10
11
12
import pytest
from src.leap_year import LeapYear


@pytest.hookimpl()
def pytest_sessionstart(session):
leap_year = LeapYear("https://digidates.de/api/v1/leapyear")
if leap_year.check_connection() == 200:
print("API connection successful!")
else:
pytest.exit("API Connection Unsuccessful!")

Notice that the hook implementation is done with the decorator @pytest.hookimpl().

We created an instance of the LeapYear class and called the check_connection() method to evaluate the response status code.

Add Pytest Sessionfinish

Just like pytest_sessionstart, we’ll use the @pytest.hookimpl() decorator to mark our pytest_sessionfinish hook.

The pytest_sessionfinish hook executes a code block after the test session is complete. In our case, we’ll simply write a message indicating that the tests have finished successfully.

But you could take this to any level you want, like sending a Slack message, generating a report, or cleaning up resources.

tests/conftest.py

1
2
3
@pytest.hookimpl()
def pytest_sessionfinish(session):
print("\nTest session finished!")

Running the Test

Let’s quickly take a look at what a successful test execution looks like.

1
pytest -v

pytest sessionstart sessionfinish

Here, we start with a pre-session message indicating that the API connection is ready for the test. Once that’s confirmed, the test collection and execution begin.

Now, let’s see what happens during a failed session. We purposefully closed the network to simulate a network error.

pytest sessionstart sessionfinish

Notice that the pre-session message indicates that the API connection has failed, automatically stopping the test collection and execution.

Very handy, right?

Passing CLI Arguments to Pytest Sessionstart

A complex codebase can operate in multiple modes or require specific input fields. We manage this by providing users with options known as CLI arguments or flags.

How do you pass these CLI arguments or flags in the Pytest sessionstart hook?

You might already be familiar with the Pytest addoption feature, which allows you to pass custom CLI arguments to your test. We’ll be combining this with the Pytest sessionstart hook.

Check out this brief guide on Pytest addoption if you need a refresher.

Here’s how we’re going to use Pytest addoption with the Pytest sessionstart hook:

tests/conftest.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pytest
from src.leap_year import LeapYear

def pytest_addoption(parser):
parser.addoption("--api_url", action="store", default="https://digidates.de/api/v1/leapyear", help="Provide API url")


@pytest.hookimpl()
def pytest_sessionstart(session):
leap_year = LeapYear(session.config.getoption("--api_url"))
if leap_year.check_connection() == 200:
print("API connection successful!")
else:
pytest.exit("API Connection Unsuccessful!")

We included a custom CLI argument --api_url using the pytest_addoption feature.

Instead of using the request fixture to get the custom option, we used the session fixture to retrieve the custom argument inside the pytest_sessionstart hook.

Let’s run the test with the custom argument:

1
pytest -v --api_url='https://digidates.de/api/v1/leapyear'

pytest sessionstart sessionfinish

Best Practices for Session Management in Pytest

Let’s quickly dive into some best practices for good session management:

  • Use Session-Scoped Fixtures Wisely:

    • Ideal for resources like database connections, external services, and web servers that are costly to set up and tear down.
    • Ensure these fixtures are only for resources that need to be shared across the entire test session and maintain test isolation and randomness.
  • Use Pytest Sessionstart and Configure Wisely:

    • pytest_sessionstart: For session-level setups like initializing databases or starting services, especially for complex setups or conditional checks based on the test environment.
    • pytest_configure: For setting up or modifying global settings, initializing plugins, and customizing hooks and commands.
  • Optimize Resource Cleanup:

    • Avoid resource leaks by cleaning up properly after tests.
    • Use the yield statement in fixtures to clearly separate setup and teardown.

By following these practices, you can ensure efficient and reliable session management in your testing process.

Wrapping Up

That’s all on this topic.

In this article, we delved into the powerful Pytest session management hooks: pytest_sessionstart and pytest_sessionfinish.

We explored these hooks in detail, uncovering their benefits and practical applications through hands-on examples.

You learned how to add custom options to the pytest_sessionstart hook, enhancing your ability to tailor the testing process to your specific needs.

Additionally, we shared valuable best practices on session management, ensuring your tests run smoothly and efficiently.

The Pytest session management hooks empower you to execute pre-session tasks, setting the stage for improved test execution.

As you move forward, I encourage you to experiment with these techniques and integrate them into your workflow for even greater testing success.

If you have questions, suggestions, or topics you’d like me to cover in future articles, feel free to reach out via Twitter, GitHub, or Email.

Until next time, happy testing, and may your code always perform as intended! Cheers! 🚀

Additional Reading

Example Code Repository
What is Setup and Teardown in Pytest? (Importance of a Clean Test Environment)
What Is The pytest_configure Hook? (A Simple Guide)
A Simple Guide to Fixing The ‘Pytest Collected 0 Items’” Error
How To Use Pytest With Command Line Options (Easy To Follow Guide)
How To Run Pytest With Poetry (A Step-by-Step Guide)
Introduction To Pytest Hooks (A Practical Guide For Beginners)
What Are Pytest Fixture Scopes? (How To Choose The Best Scope For Your Test)
A Simple Guide to Fixing The ‘Pytest Collected 0 Items’” Error
Pytest API Reference