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
The pytest fixture protocol: faker_seed, faker_locale, and conftest.py
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
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.