Skip to content

Intermediate

Level: Intermediate🚴🏻 ~ Level: Advanced🚀

Fixture

If many of your tests share the same dependency, pytest provide a convenient way to facilitate such situation: fixture.

Dependency

The dependency can be a data, an object, a database record, a function or a setup teardown routine.

From the official site, it describes fixture as:

In testing, a fixture provides a defined, reliable and consistent context for the tests. This could include environment (for example a database configured with known parameters) or content (such as a dataset).

Readings

Try this out

Here is a very simple case to show how to use fixtures.

Setup the files as below:

.
├── cats.py
└── test_cats.py
cats.py
1
2
3
4
5
6
7
8
9
class Cat:
    def eats(self, food: str) -> bool:
        return food in ["cookies", "candies", "fish", "apples"]

    def drinks(self, drink: str) -> bool:
        return drink in ["milk", "water", "juice"]

    def chase(self, thing: str) -> bool:
        return thing in ["mouse", "cat", "bird", "laser pointer"]
test_cats.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def test_cat_eats():
    cat = Cat()
    assert cat.eats("fish")

def test_cat_drinks():
    cat = Cat()
    assert not cat.drinks("coffee")

def test_cat_chases():
    cat = Cat()
    assert not cat.chases("fish")

In the highlighted lines in test_cats.py, we need to create the instance of Cat multiple times.

Let's rewrite the test with a fixture. We need to create a new file conftest.py.

.
├── conftest.py
├── cats.py
└── test_cats.py
conftest.py
1
2
3
4
5
6
7
import pytest
from cats import Cat


@pytest.fixture
def cat():
    return Cat()
cats.py
1
2
3
4
5
6
7
8
9
class Cat:
    def eats(self, food: str) -> bool:
        return food in ["cookies", "candies", "fish", "apples"]

    def drinks(self, drink: str) -> bool:
        return drink in ["milk", "water", "juice"]

    def chase(self, thing: str) -> bool:
        return thing in ["mouse", "cat", "bird", "laser pointer"]
test_cats.py
1
2
3
4
5
6
7
8
def test_cat_eats(cat):
    assert cat.eats("fish")

def test_cat_drinks(cat):
    assert not cat.drinks("coffee")

def test_cat_chases(cat):
    assert not cat.chases("fish")

With a fixture, we can directly use the name of the fixture as an argument of our test function.

Now let's add more test cases using pytest.mark.parametrize introduced in the previous tutorial.

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


@pytest.mark.parametrize(
    "food, result",
    [
        ("fish", True),
        ("cookies", True),
        ("laptop", False),
    ],
)
def test_cat_eats(food, result, cat):
    assert cat.eats(food) == result


@pytest.mark.parametrize(
    "drink, result",
    [
        ("milk", True),
        ("water", True),
        ("coffee", False),
    ],
)
def test_cat_drinks(drink, result, cat):
    assert cat.drinks(drink) == result


@pytest.mark.parametrize(
    "thing, result",
    [
        ("cat", True),
        ("mouse", True),
        ("crocodile", False),
    ],
)
def test_cat_chases(thing, result, cat):
    assert cat.chases(thing) == result

Now, run pytest:

$ pytest test_cat.py

collecting ...
 test_cat.py ✓✓✓         100% ██████████

Results (0.03s):
      9 passed

You perhaps have a sense of what fixtures is capable now, there are some further practice you can continue to do by yourself.

Remember to Do the Reading

Read the content recommended in the reading sections.

It's important to learn how to use a tool, and more important to understand a tool.

Further Practice

  • Write a simple fixture
  • Use multiple fixtures
  • Use fixtures inside fixtures
  • Autouse a fixture
  • Write fixtures with different scopes
  • Setup and teardown
  • Parametrize fixtures