Best Practices for UUID Generators in Testing
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 โ