Skip to content

Marker API

The @pytest.mark.freeze_uuid marker integrates with pytest's marker system for declarative UUID mocking.

Version-Specific Markers

Use version-specific markers for clarity and to enable version-specific features:

Marker UUID Version Extra Parameters
@pytest.mark.freeze_uuid4(...) uuid4 (recommended) -
@pytest.mark.freeze_uuid1(...) uuid1 node, clock_seq
@pytest.mark.freeze_uuid6(...) uuid6 node, clock_seq
@pytest.mark.freeze_uuid7(...) uuid7 -
@pytest.mark.freeze_uuid8(...) uuid8 -
@pytest.mark.freeze_uuid(...) uuid4 (backward compatibility) -
import uuid
import pytest

@pytest.mark.freeze_uuid4("12345678-1234-4678-8234-567812345678")
def test_uuid4():
    assert str(uuid.uuid4()) == "12345678-1234-4678-8234-567812345678"

@pytest.mark.freeze_uuid1(seed=42, node=0x123456789abc)
def test_uuid1_with_node():
    result = uuid.uuid1()
    assert result.node == 0x123456789abc

@pytest.mark.freeze_uuid7(seed=42)
def test_uuid7():
    result = uuid.uuid7()
    assert result.version == 7

Stacking Multiple Version Markers

Mock multiple UUID versions in the same test:

@pytest.mark.freeze_uuid4("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")
@pytest.mark.freeze_uuid1(seed=42)
def test_multiple_versions():
    assert str(uuid.uuid4()) == "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
    result1 = uuid.uuid1()
    assert result1.version == 1

Basic Usage

import uuid
import pytest

@pytest.mark.freeze_uuid("12345678-1234-4678-8234-567812345678")
def test_with_marker():
    assert str(uuid.uuid4()) == "12345678-1234-4678-8234-567812345678"

Multiple UUIDs

@pytest.mark.freeze_uuid([
    "11111111-1111-4111-8111-111111111111",
    "22222222-2222-4222-8222-222222222222",
])
def test_sequence():
    assert str(uuid.uuid4()) == "11111111-1111-4111-8111-111111111111"
    assert str(uuid.uuid4()) == "22222222-2222-4222-8222-222222222222"

Seeded UUIDs

@pytest.mark.freeze_uuid(seed=42)
def test_seeded():
    result = uuid.uuid4()
    assert result.version == 4

The seed="node" option derives the seed from the test's fully-qualified name:

@pytest.mark.freeze_uuid(seed="node")
def test_node_seeded():
    # This test always produces the same UUIDs
    # Different tests get different sequences
    result = uuid.uuid4()
    assert result.version == 4

Why node seeding is recommended

Node-seeded UUIDs give you deterministic, reproducible tests without the maintenance burden of hardcoded UUIDs. Each test gets its own unique seed derived from its fully-qualified name (e.g., test_module.py::TestClass::test_method), so tests are isolated and don't affect each other.

Class-Level Marker

Apply to all methods in a test class:

@pytest.mark.freeze_uuid(seed="node")
class TestUserService:
    def test_create(self):
        # Seed derived from "test_module.py::TestUserService::test_create"
        result = uuid.uuid4()
        assert result.version == 4

    def test_update(self):
        # Seed derived from "test_module.py::TestUserService::test_update"
        result = uuid.uuid4()
        assert result.version == 4

Module-Level Marker

Apply to all tests in a module using pytestmark:

# tests/test_user_creation.py
import uuid
import pytest

pytestmark = pytest.mark.freeze_uuid(seed="node")


def test_create_user():
    # Seed derived from "test_user_creation.py::test_create_user"
    result = uuid.uuid4()
    assert result.version == 4


def test_create_admin():
    # Seed derived from "test_user_creation.py::test_create_admin"
    result = uuid.uuid4()
    assert result.version == 4

Exhaustion Behavior

import uuid
import pytest
from pytest_uuid import UUIDsExhaustedError

@pytest.mark.freeze_uuid(
    "11111111-1111-4111-8111-111111111111",
    on_exhausted="raise",
)
def test_exhaustion():
    uuid.uuid4()  # OK
    with pytest.raises(UUIDsExhaustedError):
        uuid.uuid4()  # Raises

Ignoring Modules

@pytest.mark.freeze_uuid("12345678-1234-4678-8234-567812345678", ignore=["celery"])
def test_with_ignored():
    pass

Opting Out of Default Ignores

By default, packages like botocore are always ignored. Use ignore_defaults=False to mock them:

@pytest.mark.freeze_uuid("12345678-1234-4678-8234-567812345678", ignore_defaults=False)
def test_mock_everything():
    # All uuid.uuid4() calls are mocked, including from botocore
    pass

Session-Level Configuration

For session-wide mocking, use a session-scoped autouse fixture in conftest.py:

# conftest.py
import hashlib

import pytest
from pytest_uuid import freeze_uuid


@pytest.fixture(scope="session", autouse=True)
def freeze_uuids_globally(request):
    # Use hashlib for deterministic seeding across processes.
    # Python's hash() is randomized per-process via PYTHONHASHSEED:
    # https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHASHSEED
    #
    # Convert node ID to a deterministic integer seed:
    # 1. hashlib.sha256() creates a hash of the node ID string
    # 2. .hexdigest() returns the hash as a 64-char hex string
    # 3. [:16] takes first 16 hex chars (64 bits) - plenty of uniqueness
    # 4. int(..., 16) converts hex string to integer
    node_bytes = request.node.nodeid.encode()
    seed = int(hashlib.sha256(node_bytes).hexdigest()[:16], 16)
    with freeze_uuid(seed=seed):
        yield

Note

For session-level fixtures, use request.node.nodeid directly since seed="node" in the marker requires per-test context. Always use hashlib (not hash()) for node-derived seeds, as Python's built-in hash() is randomized per-process.

Parameters

Parameter Type Description
uuids str, UUID, or sequence UUID(s) to return
seed int, Random, or "node" Seed for reproducible generation
on_exhausted str "cycle", "random", or "raise"
ignore list[str] Module prefixes to exclude from mocking
ignore_defaults bool Include default ignore list (default True)
node int Fixed MAC address for uuid1/uuid6 markers
clock_seq int Fixed clock sequence for uuid1/uuid6 markers