Skip to main content

Python Test Data Generator: Custom Faker Providers and pytest Fixture Wiring

Python's Faker library generates realistic fixture data in a test file, but most tutorials stop at `fake.name()` printed to stdout. This guide shows how to subclass `BaseProvider` with your own domain vocabulary and wire the result through pytest's `faker_seed` fixture so every run in your suite is deterministic and reproducible.

Libraries confirmed

Faker (pip install Faker) with faker.providers.BaseProvider, plus the Faker pytest plugin fixtures. Doc pages retrieved 2026-08-01: faker.readthedocs.io index, pytest-fixtures, and BaseProvider pages. Do not pin a patch version on the page; date the retrieval instead.

The pytest fixture protocol: faker_seed, faker_locale, and conftest.py

The Faker pytest plugin exposes a session-scoped faker fixture that reseeds to 0 before each test by default. Defining a faker_seed fixture that returns an integer overrides that seed without calling a setter. Defining faker_locale downgrades the session-scoped instance to function-scoped. Keep .unique for large spaces (email prefixes, UUIDs); small enums like a three-value support_tier will exhaust and raise UniquenessException (Faker docs, retrieved 2026-08-01).

Runnable code sample

Verified against library docs retrieved 2026-08-01. Copy into your test tree and adjust domain vocabulary as needed.

# conftest.py
import pytest
from faker.providers import BaseProvider


class SupportTierProvider(BaseProvider):
    """Domain vocabulary Faker does not ship: our own billing tiers."""

    def support_tier(self) -> str:
        return self.random_element(elements=("free", "team", "enterprise"))

    def seat_count(self) -> int:
        return self.random_int(min=1, max=250)


@pytest.fixture(scope="session", autouse=True)
def faker_seed():
    # Every test in the session gets the same seeded Faker instance.
    return 12345


@pytest.fixture
def account_faker(faker):
    faker.add_provider(SupportTierProvider)
    return faker


# test_accounts.py
def test_account_rows_are_unique_and_bounded(account_faker):
    rows = [
        {
            "email": f"{account_faker.unique.first_name().lower()}@example.test",
            "tier": account_faker.support_tier(),
            "seats": account_faker.seat_count(),
        }
        for _ in range(50)
    ]
    assert len({row["email"] for row in rows}) == 50
    assert all(1 <= row["seats"] <= 250 for row in rows)

When to use Generate-Data instead

Use in-process libraries when fixtures must live next to assertions in CI. Use thefree generatorwhen you need a downloadable file, labeled duplicates with Master ID / Duplicate Type, or exports beyond what your library emits. See thegenerator comparisonandexport formats guide.

Frequently asked questions

How do I make Faker return the same data on every pytest run?

Define a `faker_seed` fixture in your `conftest.py` that returns an integer. The Faker pytest plugin reads this fixture before each test and uses the value to reseed its session-scoped `Faker` instance. You do not call a setter directly. The "Seeding Configuration" heading of https://faker.readthedocs.io/en/master/pytest-fixtures.html explains that the `faker` fixture is "reseeded using a seed value of `0` prior to each test" by default, and that you override it by defining `faker_seed`; this was confirmed on 2026-08-01.

How do I add a data type Faker does not ship, such as my own account tiers?

Subclass `faker.providers.BaseProvider` and implement methods using `self.random_element(elements=(...))` or `self.random_int(min=, max=)`, then register the provider on your Faker instance with `fake.add_provider(YourProvider)`. The "How to create a Provider" entry in the Faker docs index (https://faker.readthedocs.io/en/master/index.html, retrieved 2026-08-01) shows this pattern. A "How to create a Dynamic Provider" entry covers pulling vocabulary from a database rather than a hardcoded tuple.

Why do I get duplicate emails, and what does `.unique` actually guarantee?

`.unique` does not guarantee infinite uniqueness. The Faker docs "Unique values" section states that `.unique` raises `UniquenessException` after a number of attempts and warns about the birthday paradox: the chance of a collision rises quickly as the generated space shrinks relative to the number of rows you need (https://faker.readthedocs.io/en/master/index.html, retrieved 2026-08-01). The MotherDuck post (https://motherduck.com/blog/python-faker-duckdb-exploration/, observed 2026-08-01) comments inline that an SSN value "is not guaranteed to be unique, so you might want" to override it. The practical fix: keep `.unique` for fields with large spaces (email prefixes, UUIDs), not for fields like `support_tier` that have only 3 possible values.

More Python data engineering guides