Testing With UUIDs

One problem with the use of final is the inability to create a mock object to use in tests. However, the following techniques should help with testing.

Tip

To learn why ramsey/uuid uses final, take a look at Why does ramsey/uuid use final?.

Inject a UUID of a Specific Type

Let’s say we have a method that uses a type hint for UuidV1.

public function tellTime(UuidV1 $uuid): string
{
    return $uuid->getDateTime()->format('Y-m-d H:i:s');
}

Since this method uses UuidV1 as the type hint, we’re not able to pass another object that implements UuidInterface, and we cannot extend or mock UuidV1, so how do we test this?

One way is to use Uuid::uuid1() to create a regular UuidV1 instance and pass it.

public function testTellTime(): void
{
    $uuid = Uuid::uuid1();
    $myObj = new MyClass();

    $this->assertIsString($myObj->tellTime($uuid));
}

This might satisfy our testing needs if we only want to assert that the method returns a string. If we want to test for a specific string, we can do that, too, by generating a UUID ahead of time and using it as a known value.

public function testTellTime(): void
{
    // We generated this version 1 UUID ahead of time and know the
    // exact date and time it contains, so we can use it to test the
    // return value of our method.
    $uuid = Uuid::fromString('177ef0d8-6630-11ea-b69a-0242ac130003');
    $myObj = new MyClass();

    $this->assertSame('2020-03-14 20:12:12', $myObj->tellTime($uuid));
}

Note

These examples assume the use of PHPUnit for tests. The concepts will work no matter what testing framework you use.

Returning Specific UUIDs From a Static Method

Sometimes, rather than pass UUIDs as method arguments, we might call the static methods on the Uuid class from inside the method we want to test. This can be tricky to test.

public function tellTime(): string
{
    $uuid = Uuid::uuid1();

    return $uuid->getDateTime()->format('Y-m-d H:i:s');
}

We can call this in a test and assert that it returns a string, but we can’t return a specific UUID value from the static method call — or can we?

We can do this by overriding the default factory.

First, we create our own factory class for testing. In this example, we extend UuidFactory, but you may create your own separate factory class for testing, as long as you implement Ramsey\Uuid\UuidFactoryInterface.

namespace MyPackage;

use Ramsey\Uuid\UuidFactory;
use Ramsey\Uuid\UuidInterface;

class MyTestUuidFactory extends UuidFactory
{
    public $uuid1;

    public function uuid1($node = null, ?int $clockSeq = null): UuidInterface
    {
        return $this->uuid1;
    }
}

Now, from our tests, we can replace the default factory with our new factory, and we can even change the value returned by the uuid1() method for our tests.

/**
 * @runInSeparateProcess
 * @preserveGlobalState disabled
 */
public function testTellTime(): void
{
    $factory = new MyTestUuidFactory();
    Uuid::setFactory($factory);

    $myObj = new MyClass();

    $factory->uuid1 = Uuid::fromString('177ef0d8-6630-11ea-b69a-0242ac130003');
    $this->assertSame('2020-03-14 20:12:12', $myObj->tellTime());

    $factory->uuid1 = Uuid::fromString('13814000-1dd2-11b2-9669-00007ffffffe');
    $this->assertSame('1970-01-01 00:00:00', $myObj->tellTime());
}

Attention

The factory is a static property on the Uuid class. By replacing it like this, all uses of the Uuid class after this point will continue to use the new factory. This is why we must run the test in a separate process. Otherwise, this could cause other tests to fail.

Running tests in separate processes can significantly slow down your tests, so try to use this technique sparingly, and if possible, pass your dependencies to your objects, rather than creating (or fetching them) from within. This makes testing easier.

Mocking UuidInterface

Another technique for testing with UUIDs is to mock UuidInterface.

Consider a method that accepts a UuidInterface.

public function tellTime(UuidInterface $uuid): string
{
    return $uuid->getDateTime()->format('Y-m-d H:i:s');
}

We can mock UuidInterface, passing that mocked value into this method. Then, we can make assertions about what methods were called on the mock object. In the following example test, we don’t care whether the return value matches an actual date format. What we care about is that the methods on the UuidInterface object were called.

public function testTellTime(): void
{
    $dateTime = Mockery::mock(DateTime::class);
    $dateTime->expects()->format('Y-m-d H:i:s')->andReturn('a test date');

    $uuid = Mockery::mock(UuidInterface::class, [
        'getDateTime' => $dateTime,
    ]);

    $myObj = new MyClass();

    $this->assertSame('a test date', $myObj->tellTime($uuid));
}

Note

One of my favorite mocking libraries is Mockery, so that’s what I use in these examples. However, other mocking libraries exist, and PHPUnit provides built-in mocking capabilities.