โ† Back to Blog

Best Practices for UUID Generators in Testing

2026-04-20 ยท 5 min read

Challenges of UUID Usage in Testing

Using UUID in testing faces several unique challenges: randomness causing test instability โ€” if test assertions depend on specific UUID values, different UUIDs generated each run will cause snapshot tests to fail; test data management โ€” large numbers of random UUIDs make test data difficult to track and reproduce; isolation in concurrent tests โ€” when multiple concurrent tests share a database, UUIDs generated by different tests need proper isolation; debugging difficulty โ€” random UUIDs in logs are hard to correlate to specific test cases.

Fixed UUIDs: Making Tests Reproducible

For tests requiring stability, use fixed (hardcoded) UUIDs rather than randomly generated ones: define a set of known UUIDs in test constant files, use these constants in test fixtures and mocks; use UUID v5 to derive deterministic UUIDs from test names or sequence numbers (same test always produces same UUID, and different tests have different UUIDs); in unit tests, mock UUID generation functions to return fixed values, allowing testing of downstream logic.

# Python ๆต‹่ฏ•ๅธธ้‡ๆ–‡ไปถ (test_constants.py)
import uuid

# ๅ›บๅฎš็š„ๆต‹่ฏ• UUID๏ผˆไธ่ฆ้šๆœบ็”Ÿๆˆ๏ผ‰
USER_UUID_1 = uuid.UUID('00000000-0000-4000-8000-000000000001')
USER_UUID_2 = uuid.UUID('00000000-0000-4000-8000-000000000002')
ORDER_UUID_1 = uuid.UUID('00000000-0000-4000-8000-000000000101')
ORDER_UUID_2 = uuid.UUID('00000000-0000-4000-8000-000000000102')

# ๆˆ–ไฝฟ็”จ v5 ไปŽๅ็งฐๆดพ็”Ÿ๏ผˆๆฏๆฌก็ป“ๆžœ็›ธๅŒ๏ผ‰
TEST_NAMESPACE = uuid.UUID('12345678-0000-0000-0000-000000000000')

def test_uuid(name: str) -> uuid.UUID:
    """ไปŽๆต‹่ฏ•ๅ็งฐๆดพ็”Ÿ็กฎๅฎšๆ€ง UUID"""
    return uuid.uuid5(TEST_NAMESPACE, name)

USER_1_ID = test_uuid('user-1')   # ๆฏๆฌก่ฟ่กŒ็ป“ๆžœ็›ธๅŒ
ORDER_1_ID = test_uuid('order-1') # ไธๅŒๅ็งฐไบง็”ŸไธๅŒ UUID

Mocking UUID Generators

# Python + pytest๏ผšmock uuid4
from unittest.mock import patch
import uuid
import pytest

def create_user():
    user_id = uuid.uuid4()  # ็”Ÿไบงไปฃ็ ไธญ็š„ UUID ็”Ÿๆˆ
    return {'id': str(user_id), 'name': 'Alice'}

def test_create_user_with_fixed_uuid():
    fixed_uuid = uuid.UUID('12345678-1234-4321-8000-000000000001')

    with patch('uuid.uuid4', return_value=fixed_uuid):
        user = create_user()
        assert user['id'] == '12345678-1234-4321-8000-000000000001'
        # ็Žฐๅœจๅฏไปฅๅฏน็กฎๅฎš็š„ UUID ๅ€ผๅšๆ–ญ่จ€

# Jest (JavaScript)๏ผš
# jest.mock('crypto', () => ({
#   randomUUID: jest.fn().mockReturnValue('12345678-1234-4321-8000-000000000001')
# }));

UUID Data Isolation in Integration Tests

# ไฝฟ็”จๅ”ฏไธ€ๅ‰็ผ€้š”็ฆปไธๅŒๆต‹่ฏ•็š„ๆ•ฐๆฎ
import uuid
import pytest

class TestPrefix:
    """ไธบๆฏไธชๆต‹่ฏ•่ฟ่กŒ็”Ÿๆˆๅ”ฏไธ€ๅ‰็ผ€๏ผŒ้ฟๅ…ๆต‹่ฏ•้—ดๆ•ฐๆฎๆฑกๆŸ“"""

    def __init__(self):
        # ๆฏๆฌกๆต‹่ฏ•่ฟ่กŒๆœ‰ๅ”ฏไธ€ ID
        self.run_id = str(uuid.uuid4())[:8]

    def make_uuid(self, entity_type: str, num: int) -> str:
        """็”Ÿๆˆๅธฆๅ‰็ผ€็š„ UUID๏ผŒๆ–นไพฟ่ฟฝ่ธชๆต‹่ฏ•ๆ•ฐๆฎ"""
        # ๆ ผๅผ๏ผš{run_id}-{entity}-{num:04d}-...
        # ็”จไบŽๅœจๆ•ฐๆฎๅบ“ไธญๅŒบๅˆ†ไธๅŒๆต‹่ฏ•่ฟ่กŒ็š„ๆ•ฐๆฎ
        seed = f"{self.run_id}-{entity_type}-{num}"
        namespace = uuid.UUID('00000000-0000-0000-0000-000000000000')
        return str(uuid.uuid5(namespace, seed))

@pytest.fixture
def test_prefix():
    return TestPrefix()

def test_create_multiple_users(test_prefix, db):
    users = []
    for i in range(5):
        user_id = test_prefix.make_uuid('user', i)
        users.append({'id': user_id, 'name': f'User {i}'})

    db.insert_many('users', users)
    # ๆ–ญ่จ€...
    # ๆต‹่ฏ•็ป“ๆŸๅŽๅฏไปฅ็”จ run_id ๅ‰็ผ€ๆธ…็†ๆ•ฐๆฎ

Handling UUID in Snapshot Tests

# pytest-snapshot ไธญๅค„็†้šๆœบ UUID
import re

def normalize_uuids(text: str) -> str:
    """ๅฐ†ๆ–‡ๆœฌไธญๆ‰€ๆœ‰ UUID ๆ›ฟๆขไธบๅ ไฝ็ฌฆ๏ผŒไฝฟๅฟซ็…ง็จณๅฎš"""
    uuid_pattern = r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
    return re.sub(uuid_pattern, '<UUID>', text, flags=re.IGNORECASE)

def test_api_response_snapshot(client, snapshot):
    response = client.get('/api/users')
    # ่ง„่ŒƒๅŒ– UUID ๅŽๅ†ๅšๅฟซ็…งๅฏนๆฏ”
    normalized = normalize_uuids(response.json())
    snapshot.assert_match(normalized, 'users_response')

# Jest + inline snapshot
# expect(response.body).toMatchInlineSnapshot(`
#   {
#     "id": "<UUID>",  // ไฝฟ็”จ serializer ๆ›ฟๆข UUID
#     "name": "Alice",
#   }
# `)

# ไฝฟ็”จ jest-serializer-regex ๆˆ–่‡ชๅฎšไน‰ serializer
# expect.addSnapshotSerializer({
#   test: (val) => typeof val === 'string' && /^[0-9a-f-]{36}$/.test(val),
#   print: () => '"<UUID>"'
# });

Generating UUID Sets for Testing

# ไธบไธๅŒๆต‹่ฏ•ๅœบๆ™ฏๅ‡†ๅค‡ UUID ๅทฅๅ…ท
import uuid
from typing import Iterator

def uuid_counter(start: int = 1) -> Iterator[str]:
    """็”Ÿๆˆ้กบๅบๆŽ’ๅˆ—็š„ๅฏ่ฏป UUID๏ผˆๆ–นไพฟ่ฐƒ่ฏ•๏ผ‰"""
    # ๆ ผๅผ๏ผš00000000-0000-4000-8000-{num:012x}
    num = start
    while True:
        yield f'00000000-0000-4000-8000-{num:012x}'
        num += 1

# ไฝฟ็”จ
gen = uuid_counter()
id1 = next(gen)  # 00000000-0000-4000-8000-000000000001
id2 = next(gen)  # 00000000-0000-4000-8000-000000000002

# ๆ‰น้‡ๅ‡†ๅค‡ๆต‹่ฏ•ๆ•ฐๆฎ
def make_test_users(count: int) -> list:
    gen = uuid_counter()
    return [
        {'id': next(gen), 'name': f'Test User {i}', 'email': f'user{i}@test.com'}
        for i in range(1, count + 1)
    ]

# ๆต‹่ฏ•ไธญไฝฟ็”จ
users = make_test_users(10)
print(users[0])  # {'id': '00000000-...001', 'name': 'Test User 1', ...}

UUID Strategy in E2E Tests

In end-to-end (E2E) tests, UUID handling strategy differs from unit tests: E2E tests typically interact with real systems, with UUIDs actually generated by the system; tests should extract UUIDs from API responses and use them in subsequent requests (rather than hardcoding); in Cypress or Playwright, pass UUIDs through variables: first POST to create a resource and obtain UUID, then use that UUID for subsequent operations; test assertions should check UUID format (whether valid) rather than specific values (which differ each run); when cleaning up database data before and after tests, use timestamp prefixes or specific fields (like is_test_data=true) to identify test data for batch cleanup.

Try the free tool now

Use Free Tool โ†’