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.
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
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.
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.
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.
Adding -v gives the results more verbosity and detail to our tests. We can now see which specific tests have failed or passed.
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.
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:
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 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
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
What to read next
Python Test Automation: Six Options for More Efficient Tests