Skip to content

[Uid] Add MockUuid/UuidFactory for deterministic UUID generation in tests #60978

Open
@OskarStark

Description

@OskarStark

Description

Similar to the Clock component's ClockInterface and MockClock, it would be beneficial to have a way to mock UUID generation for testing purposes.

Problem

Currently, when testing code that generates UUIDs, developers face challenges:

  • UUIDs are non-deterministic by design
  • Tests cannot assert exact UUID values
  • Snapshot testing becomes difficult
  • Tests rely on regex patterns rather than exact matches

Proposed Solution

Introduce a mockable UUID generation mechanism, similar to the Clock component:

interface UuidFactoryInterface
{
    public function v4(): UuidV4;
    public function v7(): UuidV7;
    // ... other versions
}

class MockUuidFactory implements UuidFactoryInterface
{
    private array $sequence = [];
    private int $index = 0;
    
    public function setSequence(array $uuids): void
    {
        $this->sequence = $uuids;
        $this->index = 0;
    }
    
    public function v4(): UuidV4
    {
        if (!isset($this->sequence[$this->index])) {
            throw new \RuntimeException('No more UUIDs in sequence');
        }
        
        return UuidV4::fromString($this->sequence[$this->index++]);
    }
}

Use Case Example

class UserService
{
    public function __construct(
        private UuidFactoryInterface $uuidFactory = new UuidFactory()
    ) {}
    
    public function createUser(string $email): User
    {
        return new User(
            id: $this->uuidFactory->v4(),
            email: $email
        );
    }
}

// In tests:
$mockFactory = new MockUuidFactory();
$mockFactory->setSequence([
    '550e8400-e29b-41d4-a716-446655440000',
    '6ba7b810-9dad-11d1-80b4-00c04fd430c8'
]);

$service = new UserService($mockFactory);
$user = $service->createUser('test@example.com');

$this->assertSame('550e8400-e29b-41d4-a716-446655440000', (string) $user->getId());

Benefits

  1. Deterministic tests - Know exactly what UUIDs will be generated
  2. Better assertions - Test exact values instead of patterns
  3. Consistency with Clock component - Follows established patterns
  4. Easier debugging - Predictable UUIDs in test scenarios

Questions for Discussion

  1. Should this be part of the Uid component or a separate testing utility?
  2. Should we support all UUID versions or just commonly used ones (v4, v7)?
  3. Should we provide a global static setter like Uuid::setFactory() similar to how some libraries handle it?
  4. How should we handle edge cases (running out of mocked UUIDs)?

Scope and Limitations

Important: This RFC specifically addresses UUID generation that happens through a public API (dependency injection, factories, etc.). It does not intend to cover:

  • UUIDs created directly via static methods (e.g., Uuid::v4()) without using the factory
  • Private/internal UUID instantiation within third-party libraries
  • Legacy code that directly instantiates UUID objects

For such cases, developers would need to refactor their code to use the factory pattern to benefit from this mocking capability.

Additional Context

This pattern is already established in Symfony with:

  • ClockInterface and MockClock in the Clock component
  • Similar patterns exist in other frameworks/libraries

Looking forward to community feedback on this proposal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    RFCRFC = Request For Comments (proposals about features that you want to be discussed)Uid

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions