How to Auto-Request Pytest Fixtures Using "Autouse"

Creating a dynamic and efficient testing environment is pivotal to seamless execution of test cases.

Fixtures, a key feature in Pytest, play a crucial role in managing the setup and teardown of various test resources, from class initialization to database and network connections.

They help solve repetitive tasks, such as handling input data or establishing database connections.

Typically, developers explicitly pass fixtures into unit tests, enabling precise control over which fixtures are executed.

However, imagine a scenario where a fixture automatically executes for all test functions, eliminating the need for explicit fixture passing…

Enter Pytest’s fixture autouse feature, a solution to this common challenge.

With fixture autouse, you can define a fixture that automatically runs before each test method, eliminating the need for manual fixture invocation in each test function.

This is especially useful when you have nested fixtures that need to be executed before each test function.

In this article, we delve into the depths of Pytest fixture autouse, exploring its applications, optimal use cases, and methods for fine-tuned control.

A practical example will illuminate the concept, providing a hands-on understanding of how Pytest fixture autouse operates.

Furthermore, we’ll unravel advanced aspects of autouse in Pytest fixtures, elevating your testing process to a new level.

Let’s embark on this journey to unlock the potential of Pytest fixture autouse and optimize your testing workflow.

Example Code

What You’ll Learn

By the end of this tutorial, you should

  • Have a crystal clear understanding of Pytest Fixtures and how to auto-request them as opposed to manually
  • Able to handle fixtures more professionally.
  • Control the order of fixture execution in tests.

Understanding Pytest Fixtures

Let’s understand the basics of Pytest fixtures.

Pytest Fixtures allows you to organize the test environment such as managing resources, data, and dependencies, before executing tests, making a well-organized and efficient environment.

Fixtures are defined using the @pytest.fixture decorator. It performs setup preconditions for tests, shares data across tests, and initiates teardown tasks at the end of the tests.

Let’s see a simple example of a Pytest fixture:

1
2
3
4
5
6
7
8
9
10
11
from src.network_manager import network_manager

@pytest.fixture
def network_connection():
net_connect = network_manager()
yield net_connect
# Teardown code

# Function to check network
def test_network_connection(network_connection):
assert network_connection == True

In this scenario, the network_connection() fixture is responsible for instantiating an object of the network_manager class. This fixture is then supplied to the test_network_connections() function to evaluate the network connection.

The code after yield is the fixture teardown bit (e.g. closing database connections, deleting files, etc.).

You can learn more about this in our article on Pytest Fixture Setup and Teardown.

Pytest Fixture Scopes (Controlling Fixture Lifespan)

You can define the lifespan and accessibility of a fixture by utilizing fixture scopes:

  • function - This is the default scope. It defines that the fixture will be created at the beginning of a test function and torn down at the end of the function.
  • class - This scope defines that the fixture will be created at the beginning of a class and torn down at the end of the class.
  • module - This scope defines that the fixture will be created at the beginning of a module and torn down at the end of the module.
  • session - This scope defines that the fixture will be created at the beginning of a session and torn down at the end of the session.

It’s very important to choose the right scope you want to limit the scope of a fixture to a specific test or test class. Take a look at this comprehensive guide on Pytest fixture scopes.

Autouse in Pytest Fixtures

“Autouse” is an advanced feature of Pytest Fixtures that enables your test functions to automatically utilize a fixture without explicitly requesting it.

An autouse fixture is established by setting the autouse=True parameter.

When confronted with recurring tasks that need execution before each test, like class initialization, network connections, database connections, Pytest’s autouse fixture functionality effortlessly manages these actions before the commencement of each test run.

Let’s delve into a straightforward example of an autouse fixture:

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

@pytest.fixture(autouse=True)
def set_number():
return 50

def test_example(set_number):
# Your test code here
print("Executing the test")
assert set_number == 50

In the provided code snippet, an autouse fixture named set_number() has been crafted to execute prior to the test function test_example().

The autouse fixture set_number() returns the value 50, which is then utilized by the test function test_example(). Note - you still need to pass the fixture to the test function.

Project Setup

Let’s organize our project to demonstrate a real-time example of using Pytest fixture autouse.

Pre-requisite

To follow this guide, you should have:

  • Python 3.11+ installed (recommend installing via Conda).
  • An elementary grasp of Python and Pytest.

Getting Started

This is what our example repo looks like,

pytest-autouse-fixture

To get started. clone the Github Repo here, or you can create your own repo by creating a folder and running git init to initialize it.

Example Code

Let’s start with a straightforward example that contains some number conversion functions.

src/number_conversions.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
class NumberConversions:
"""
Class converts decimal number to Binary, Octal and Hexadecimal
"""

def __init__(self, num: int) -> None:
self.num = num

def convert_to_binary(self):
"""
Converts Decimal to Binary
"""
return bin(self.num)

def convert_to_octal(self):
"""
Converts Decimal to Octal
"""
return oct(self.num)

def convert_to_hexadecimal(self):
"""
Converts Decimal to Hexadecimal
"""
return hex(self.num)

In the example code, there are four functions that convert a decimal number to binary, octal, and hexadecimal.

Test code

Here are some test codes for our example,

test/test_number_conversions.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
import pytest
from src.number_conversions import NumberConversions


# Define a fixture to yield a static number with an `autouse=True` and session scope i.e the number is created once for all the tests
@pytest.fixture(autouse=True, scope="session")
def static_number():
print("\nAUTOUSE fixture `static_number` setup...")
yield 50
print("\nAUTOUSE fixture `static_number` teardown...")


# Define a fixture to create an instance of NumberConversions with a function scope i.e the instance is created for each test function
@pytest.fixture(scope="function")
def number_converter(static_number):
return NumberConversions(static_number)


def test_convert_to_binary(number_converter):
result = number_converter.convert_to_binary()
assert result == "0b110010"


def test_convert_to_octal(number_converter):
result = number_converter.convert_to_octal()
assert result == "0o62"


def test_convert_to_hexadecimal(number_converter):
result = number_converter.convert_to_hexadecimal()
assert result == "0x32"

In the above test code, we incorporate the autouse fixture static_number(). This fixture facilitates the sharing of a static number, while the number_converter() fixture produces an instance of the static_number class.

Note that we don’t need to initialize the static_number fixture in the test function. This is because we set the autouse=True parameter, which automatically executes the fixture before the test function.

Result Analysis

Finally, let’s execute our test,

1
pytest -v -s

Now, you’ll get an output like the one below,

pytest-autouse-fixture

Let’s analyze the result.

You can easily notice that the fixture static_number() runs once before the test function. This is because we set the autouse fixture to scope="session". This is called Fixture Scope and can be easily controlled using the scope parameter.

You can read more about Pytest Fixture Scopes in our article here.

Combining Autouse Fixtures with Other Fixtures

To use regular fixtures with autouse fixtures, consider the following example:

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

@pytest.fixture(autouse=True)
def autouse_fixture():
print("\nExecuting autouse fixture")
return 5

@pytest.fixture
def normal_fixture(autouse_fixture):
print("\nExecuting normal fixture")
return autouse_fixture + 5

def test_example(normal_fixture):
print("Executing the test")
assert normal_fixture == 10

In the above example, we have integrated an autouse fixture, autouse_fixture(), with a standard fixture, normal_fixture(). In this case, the test function test_example() explicitly calls for the normal_fixture().

However, the autouse fixture autouse_fixture() is automatically executed before the test function, as it is defined with the autouse=True parameter.

Chaining Multiple Autouse Fixtures

At times, it becomes necessary to interconnect multiple fixtures to accomplish a task.

To establish an execution chain for these fixtures, wherein each fixture is executed sequentially, simply define the fixtures according to their respective positions.

Have a look at the below example,

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
import pytest

num = 5

@pytest.fixture(autouse=True)
def setup_fixture1():
print("\nExecuting autouse fixture 1")
global num
num = 1

@pytest.fixture(autouse=True)
def setup_fixture2():
print("\nExecuting autouse fixture 2")
global num
num += 2

@pytest.fixture(autouse=True)
def setup_fixture3():
print("\nExecuting autouse fixture 3")
global num
num += 3

def test_example():
print("Executing the test")
assert num == 6

Let’s break the code.

In this example, we utilize three autouse fixtures: setup_fixture1(), setup_fixture2(), and setup_fixture3(). The initial autouse fixture, setup_fixture1(), initializes the global variable num to 1.

Following this, the subsequent autouse fixture, setup_fixture2(), executes and increments the global value by 2. Finally, the third autouse fixture, setup_fixture3(), runs and increments the variable num by 3.

You’ll get the below output:

pytest-autouse-fixture

Parameterizing Autouse Fixtures

To parameterize an autouse fixture, you can use the @pytest.mark.parameterize marker.

If you’re unfamiliar with Pytest parameterization you can check out this article to learn more about the subject.

Fixtures can also be combined with parameters using Indirect Parametrization as shown in this article here.

Here is an example of parameterizing autouse fixture,

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

@pytest.fixture(autouse=True)
def multiply_fixture(request):
num = request.param
yield num

@pytest.mark.parametrize("multiply_fixture", [2, 3, 4], indirect=True)
def test_multiplication(multiply_fixture):
assert multiply_fixture*5 != None

Here, the function multiply_fixture() is an autouse fixture that is parametrized for the test function test_multiplication().indirect=True indicates indirect parameterization that allows you to define the parameters in the test function.

pytest-autouse-fixture

Disabling Autouse

Sometimes we need to disable an autouse fixture for a specific test function so the fixture may not interrupt the test or perhaps you wish to isolate the test from the fixture.

There are 2 ways to disable an autouse fixture,

The first and easiest way is to remove the autouse=True parameter from the fixture. This will disable the autouse fixture for all the test functions.

The second (more manual) way is to use the @pytest.mark.skip decorator to disable the autouse fixture for a specific test function.

You can incorporate a condition that specifies whether the fixture is permitted to run for the test.

Then, employ a Pytest marker to disable the autouse fixture in the test function where you intend to exclude it from automatic usage.

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

@pytest.fixture(autouse=True)
def conditional_setup(request):
if "skip_setup" in request.keywords:
pytest.skip("Skipping setup for marked tests")
return True

@pytest.mark.skip_setup
def test_example_with_skip(conditional_setup):
# This test will skip the autouse fixture
assert conditional_setup == True

Examine the fixture conditional_setup(), where we’ve implemented an if condition to determine whether the autouse fixture should execute for the specific test.

In the test function, we utilized the @pytest.mark.skip_setup decorator to deactivate the autouse fixture conditional_setup() fixture specifically for the test_example_with_skip() function.

Creating Order For Fixture Execution

To create an order of fixture execution, just request the previous fixture with the later fixture. Let’s see an example to make the concept easier,

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

@pytest.fixture(autouse=True, scope="function")
def b():
print("I'm first")
pass

@pytest.fixture(scope="function")
def a(b):
print("I'm Second")
pass

Now let’s see how it works. As fixture a requests fixture b, fixture a is unable to run until fixture b completes its execution. So, here b will be executed before a.

Final Thoughts

Let’s recap what we’ve learned from this article,

First, we delved into the fundamentals of Pytest fixtures, exploring their scopes. Following that, we explored the autouse feature of Pytest fixtures through a straightforward example.

Next, we examined how to combine autouse fixtures with other fixtures and how to chain multiple autouse fixtures. We also saw a practical example of a number conversion function and how to test it using autouse fixtures.

Finally, we explored how to parameterize autouse fixtures and disable them for specific test functions. We also learned how to create an order of fixture execution.

The autouse feature in Pytest fixtures is a potent tool that enhances the efficiency of your tests by simplifying the execution of recurring tasks necessary for each test function.

With its ability to automatically execute fixtures, you can streamline your testing process and eliminate the need for manual fixture invocation.

If you have any ideas for improvement or like me to cover any topics please comment below or send me a message via Twitter, GitHub or Email.

Additional Reading

Example Code
What Are Pytest Fixture Scopes? (How To Choose The Best Scope For Your Test)
How Pytest Fixtures Can Help You Write More Readable And Efficient Tests
Ultimate Guide To Pytest Markers And Good Test Management
A Step-by-Step Guide To Using Pytest Fixtures With Arguments
How to Effortlessly Generate Unit Test Cases with Pytest Parameterized Tests
Using autouse for Fixtures That Always Get Used
Pytest Fixture - autouse fixtures