Using Pytest Fixtures: A Step-by-Step Guide With Examples

Pytest has a number of great features. One of those special features is fixtures. Using pytest fixtures to test your…

Testim
By Testim,

Pytest has a number of great features. One of those special features is fixtures. Using pytest fixtures to test your application is one way you can exponentially increase code quality. Higher-quality code, plus more readable documentation, leads to a massive reduction in the cost of resources for our applications.

Pytest is one of the most popular testing modules for Python. Pytest is used for Python API test cases, database projects, artificial intelligence, and even for blockchain applications. Furthermore, pytest and its features, like fixtures, are highly configurable and doesn’t have much boilerplate. Having the ability to use pytest with fixtures alone can create a career path for any talented Python developer.

In this step-by-step guide, we’ll quickly go through how to set up pytest with fixtures. We’ll also go into detail into the different types of fixtures, with examples. By the end, you should have a good idea of how fixtures work in pytest.

pytest installation

What Are Pytest Fixtures?

Pytest fixtures are functions that can be used to manage our apps states and dependencies. Most importantly, they can provide data for testing and a wide range of value types when explicitly called by our testing software. You can use the mock data that fixtures create across multiple tests.

@pytest.fixture
def one():
   return 1

Fixtures are very flexible and have multiple uses cases. Since Python is an object-oriented programming language, we can parse different types of objects to be used as test data such as integers, strings, lists, dictionaries, booleans, classes, floats, and other complex numbers.

And did I mention that it’s free and open source? Pytest also has over 900 plugins for developers to use. You can see a complete list of them here.

How to Test Your App With Pytest Fixtures

Now that you know what pytest fixtures are, let’s see how to use them. First, you need to have pytest installed on your machine.

Install Pytest on Your Local Machine

Pytest can be installed on most Python environments, Jupyter Notebook, and Colab. The following guide assumes you’re installing it on a local machine.

virtualenv -p python3 folder_name

Start your local virtual environment. Replace pytest_example with your project folder’s name.

virtualenv -p python3 pytest_example

After creating the virtual environment, move into the new directory and activate it.

cd pytest_example
.\Scripts\activate

Run the following command to make sure that pytest is installed in your system:

pip install pytest

pip install pytest

Create the Fixtures and Pytest Files

Create at least one pytest file. Keep in mind that both methods and test files need the test_ prefix or _test suffix to be recognized as a test file.

test_file_name.py reads better than file_name_test.py. Pytest will not run file_name.py or test_file_name.py.

Import the pytest module in all Python files you want to test, as well as in any associated configuration files for the fixtures.

Import pytest

We have to indicate that the function is a fixture with @pytest.fixture. These specific Python decorations let us know that the next method is a pytest fixture.

@pytest.fixture

Implementing the simplest pytest fixture can just return an object, like an integer.

@pytest.fixture
def one():
    return 1

Either in the same file or a different test file, we can create tests that request the fixtures needed. This is how we can test their assertions. These test methods need to start with the prefix test_.

def test_we_are(one):
    assert one == 1

Run Your Pytests With Fixtures

Finally, tell pytest to test your code. Pytest will test all test files in the current directory and subdirectories with the correct prefix or suffix.

pytest

Adding -v gives the results more verbosity and detail to our tests. We can now see which specific tests have failed or passed.

pytest -v

How to Tell If a Test Failed

When tests are run, which tests fail or pass are clearly labeled. Our asserts test whether a certain logic statement is true or not. AssertionError means that the assertion is false, and that’s why the test has failed. These play a particularly important role in testing for bugs in our system. Other errors we come across will be coding errors and bad naming conventions in our test code.

pytest fixtures

Use Pytest Fixtures Across Multiple Test Files With conftest.py

To make it easier on ourselves, we can define a fixture to use across multiple test files. These fixtures are defined by creating a file called conftest.py.

import pytest

@pytest.fixture
def important_value():
    important = True
    return important

Additionally, we can call separate files that also import the fixture from our configuration file. By calling test files specifically by name, instead of running multiple test files, we reduce the number of test results that we need to read in the terminal.

import pytest

def test_if_important(important_value):
    assert important_value == True

Now, run the following command:

pytest test_important.py

test session starts

Modularization in Pytest Fixtures

Fixtures are modular. This means one or more fixtures may be dependent on another fixture. Therefore, if we change one fixture, it may result in changing the function of other fixtures. This causes our test suite to scale. This also works well in our configuration file.

One fixture simply requests the other, and hey, presto! We can combine all sorts of objects together, like strings, or do complex math.

@pytest.fixture
def me():
    return "me"

@pytest.fixture
    def together(me):
    return "you and " + me

This flexibility gives us the ability to create all sorts of different combinations of fixtures. If that doesn’t make you happy, you can even combine two or more fixtures together. So, we add this next piece of code to our configuration file. The following fixture requests two inputs from two previous fixtures.

@pytest.fixture
def complete(together, happy):
     return together + happy

This is followed by one more test to our modular test file:

def test_modular_complete(complete):
     assert complete == "you and me are happy."

We’re going to test the previous code by also demonstrating how to single out specific tests in our test suite.

testing previous code

Testing Just One Test

We can use a string to run only tests with a denoted string in the definition name. This will just be one test with a string name, like this:

pytest -k string_name

or multiple tests if we’ve used the string in many other tests as well:

pytest -v -k modular

We can, of course, use strings to name the whole test and thus successfully single out one test case:

pytest -v -k modular_complete

Pytest Fixtures Name

When we look at the default settings for fixtures, we have a few parameters we can use to further customize our tests to our needs.

@fixture(fixture_function = None, *, scope = 'function', params = None, autouse = False, ids = None, name = None)

You can look into these parameters in more detail in the pytest docs.

Additionally, we can give our fixtures names or IDs. This is helpful when we’re using the fixture in the same module. In these scenarios, we have to give these fixtures the prefix fixture_.

@pytest.fixture(name = "my_account")
def fixture_my_account():
     balance = 0
     return balance

Set Tests to Automatically Request a Fixture

We can set up all of the tests to automatically request a fixture by adding autouse=true to the fixtures decoration. Even when a test doesn’t request a fixture, it will get the input anyway:

@pytest.fixture(autouse=True)
def meaning_of_life():
    return 42

It’s All About Scope

Pytest fixtures have a range of uses for different situations. In short, different scopes destroy each fixture at different times in the tests and sessions. Each fixture is automatically defined as a function. Additionally, we can choose to use module, class, package, or session.

Here’s a more detailed explanation of fixtures scopes from the website Better Programming.

After the function, the module is the next most useful offering from fixtures. The genius behind the module is that it creates an object when a function requests the fixture. Accordingly, this object is then reused repeatedly while it’s being used by all the tests. When all the tests are complete, it’s torn down.

The advantage of the module scope is that it uses fewer resources since it only creates the object once instead of two or more times. For example, this fixture, taken from the pytest docs, helps test the SMPT connection from Gmail services. If we didn’t use the module scope, the tests would take longer to run.

@pytest.fixture(scope = "module")
def smtp_connection():
    return smtplib.SMTP("smtp.gmail.com", 587, timeout = 5)

Our Pytest Results Are Complete

test complete

We must remember that the purpose of software testing with pytest fixtures is to find bugs, not to prove that there are no bugs.

Pytest can be used in different situations and over different types of environments. The previous examples were based on a Python virtual environment. Alternatively, developers can test out Testim’s free testing cloud service. Upon request, there are also options for Salesforce.

All the coding examples are available are here.

Expand Your Test Coverage

Fast and flexible authoring of AI-powered end-to-end tests — built for scale.
Start Testing Free

What to read next

Python Test Automation: Six Options for More Efficient Tests

A Detailed Introduction and How-To for Javascript BDD