How and when to parametrize a fixture with pytest
Calling a fixture in parametrization can be very helpful sometimes. Find out how...
This post assumes you are familiar with python and pytest. I’ll do a separate series of introduction to python and pytest, but you can read on to get an idea of what I mean in an abstract sense.
So first of all what is a fixture? A fixture in software world is any thing that puts a system in a particular state. It provides useful a context prior to performing a specific test. It can be any of the following:
A database setup
Reading and parsing a config file
Calling and creating objects in certain sequence
In pytest though, the actor or object under test itself can be a fixture. Now imagine if I am writing a class Car
and a class SUV
which inherit from a common base class Vehicle
, I would write a single test suite for testing their common functionality. Let me illustrate what I mean here:
You can see I’ve a common test class which uses a fixture vehicle. Now I want vehicle to be an alias to both SUV and Car objects when I run pytest. The way to do it is to use pytest parametrization.
It can be done in two ways1:
lazy_fixtures if you are using pytest < 8.0.0
using pytest request fixture
Before going into parametrization let’s first define a fixture for both SUV and Car like so:
Now that we have our fixtures, let’s see how can we parametrize them.
Using lazy_fixture module
The good news, there is a python package called lazy fixtures. It can be used to call a fixture in parametrization simply by passing a string argument of the name of the fixture you want.
Here’s how to install pytest-lazy-fixture
pip install pytest-lazy-fixture
The caveat here is that the package doesn’t work with pytest version 8.0.0. To overcome this make sure you’ve installed pytest version less than 8.0.0. It can be done by specifying the following in your requirements.txt or with pip install command
pytest < 8.0.0
Once you’ve installed pytest-lazy-fixture, all you’ve to do is call pytest.lazy_fixture in parametrization with the fixture name as a string. You can see the final code below:
@pytest.mark.parametrize(
"vehicle", [pytest.lazy_fixture("car"), pytest.lazy_fixture("suv")]
)
class TestCommon:
def test_steering(self, vehicle):
assert vehicle.steering() == "steering"
def test_wheels(self, vehicle):
assert vehicle.wheels() == "wheels"
Using pytest request fixture
Using this method is good to keep compatibility across pytest versions. This method works with pytest < 8.0.0 and pytest == 8.0.0 and hopefully will stay in the future releases. It uses a special fixture in pytest called request, which can give more information about the current test being run. I personally like the lazy_fixture method since the code looks clean to read.
You simply have to call the request fixture in the test. Here’s the example using the request.getfixturevalue
@pytest.mark.parametrize("vehicle_type", ["car", "suv"])
class TestCommon:
def test_steering(self, vehicle_type, request):
vehicle = request.getfixturevalue(vehicle_type)
assert vehicle.steering() == "steering"
def test_wheels(self, vehicle_type, request):
vehicle = request.getfixturevalue(vehicle_type)
assert vehicle.wheels() == "wheels"
Now that you’ve seen all the possible ways of how to call a fixture dynamically in pytest parametrization, keep in mind the pros and cons of each. Do remember to use this handy feature to reuse your tests in case of testing common methods in inherited objects.