Open
Description
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
- Deterministic tests - Know exactly what UUIDs will be generated
- Better assertions - Test exact values instead of patterns
- Consistency with Clock component - Follows established patterns
- Easier debugging - Predictable UUIDs in test scenarios
Questions for Discussion
- Should this be part of the Uid component or a separate testing utility?
- Should we support all UUID versions or just commonly used ones (v4, v7)?
- Should we provide a global static setter like
Uuid::setFactory()
similar to how some libraries handle it? - 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
andMockClock
in the Clock component- Similar patterns exist in other frameworks/libraries
Looking forward to community feedback on this proposal.