Closed cmosguy closed 7 years ago
@ramsey btw, I did read your responses here: https://github.com/ramsey/uuid/issues/23 They are all very confusing and I do not understand the correct approach here.
Update: I'm not ignoring your question; I've been dealing with moving, the holidays, death in family, and lack of Internet connectivity over the past few weeks. I hope to address your question later this week. :-)
@ramsey thank you for taking the time to respond, I am sorry to hear about your loss. I hope you and your family had a great holiday. Take care.
In #23, the OP couldn't mock Ramsey\Uuid\Uuid because the class was marked final
. That restriction has since been removed, and the Uuid class may now be mocked. What you're running into is an issue mocking the return value of Uuid::uuid4()
, since it's a static method being called from within a unit under test. Fortunately, there are several ways to go about this.
Here's an example I've whipped up and tested, so I know either of these approaches will work. Let me know if you have any questions or need clarification.
<?php
use PHPUnit\Framework\TestCase;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidFactory;
class UuidMockTest extends TestCase
{
public function testKnownUuidByMockingFactory()
{
// Create a Uuid object from a known UUID string
$stringUuid = '253e0f90-8842-4731-91dd-0191816e6a28';
$uuid = Uuid::fromString($stringUuid);
// Partial mock of the factory;
// returns $uuid object when uuid4() is called.
$factoryMock = \Mockery::mock(UuidFactory::class . '[uuid4]', [
'uuid4' => $uuid,
]);
// Replace the default factory with our mock
Uuid::setFactory($factoryMock);
// Uuid::uuid4() is a proxy to the $factoryMock->uuid4() method, so
// when Uuid::uuid4() is called, it calls the mocked method on the
// factory and returns a valid Uuid object that we have defined.
$this->assertSame($uuid, Uuid::uuid4());
$this->assertEquals($stringUuid, Uuid::uuid4()->toString());
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testMockStaticUuidMethodToReturnKnownString()
{
// We replace the Ramsey\Uuid\Uuid class with one created by Mockery
// (using the "alias:" prefix) so that we can mock the static uuid4()
// method. For this to work without affecting the Ramsey\Uuid\Uuid
// class used by other tests, we must run this in a separate process
// with preserveGlobalState disabled (see method annotations).
\Mockery::mock('alias:' . Uuid::class, [
'uuid4' => 'uuid-baz',
]);
// We've replaced Ramsey\Uuid\Uuid with our own class that defines the
// method uuid4(). This method returns the string "uuid-baz."
$this->assertEquals('uuid-baz', Uuid::uuid4());
}
}
@cmosguy Did my answer help?
@ramsey thanks for taking the time for writing a very clear test in your response.
This is super informative and helpful. I am now able to use this in my workflow. I had no clue you could trick phpunit like that @preserveGlobalState disabled
and @runInSeparateProcess
so that the mocking did not affect other tests, this is really useful. I hope this issue helps someone else.
Thanks for the super support!
This is how I mocked it
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Ramsey\Uuid\UuidFactory;
$factoryMock = $this->prophesize(UuidFactory::class);
$uuidInterface = $this->prophesize(UuidInterface::class);
$factoryMock->uuid4()->shouldBeCalled()->willReturn($uuidInterface->reveal());
$uuidInterface->toString()->shouldBeCalled()->willReturn('e36f227c-2946-11e8-b467-0ed5f89f718b');
// Replace the default factory with our mock
Uuid::setFactory($factoryMock->reveal());
This is how I called UUID generator in my class Uuid::uuid4()->toString()
Also don't forget to do:
$defaultFactory = Uuid::getFactory();
...
Uuid::setFactory($defaultFactory);
after your tests are done so you don't mess up other tests/code that uses Uuid
The other day I encountered a similar problem in a project, because the default factory was overwritten inside Uuid
.
For me, relying on the Uuid
singleton inside the classes to generate UUID's instead of injecting the UuidFactory
into the classes seems a bad practice because it couples very tightly the code to this library for instance, and you end up needing to work around the root cause of the issue by adopting the solution @j4r3kb pointed out above.
This is how I did this thanks to https://github.com/baopham/laravel-dynamodb/issues/10#issuecomment-400094100 and https://github.com/ramsey/uuid/issues/147#issuecomment-977640310
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidFactory;
$defaultFactory = Uuid::getFactory();
$requestId = $this->faker->uuid;
// Partial mock of the factory
// returns $uuid object when uuid4() is called.
$factoryMock = \Mockery::mock(UuidFactory::class . '[uuid4]', [
'uuid4' => Uuid::fromString($requestId),
]);
Uuid::setFactory($factoryMock);
// set back to default
Uuid::setFactory($defaultFactory);
One more approach if you have multiple calls to the Uuid factory in the tested code but only care about the next single/few of them:
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidFactory;
use Ramsey\Uuid\UuidFactoryInterface;
use Ramsey\Uuid\UuidInterface;
class PrimedUuidFactory extends UuidFactory
{
/**
* @var array<int, UuidInterface>
*/
private array $uuidList = [];
public function __construct(
private UuidFactoryInterface $originalFactory
) {
parent::__construct();
}
public function uuid4(): UuidInterface
{
$uuid = array_shift($this->uuidList);
if ($uuid === null) {
return $this->originalFactory->uuid4();
}
return $uuid;
}
public function pushUuid(string $uuid4): void
{
$this->uuidList[] = Uuid::fromString($uuid4);
}
}
Then in your test case:
$uuidFactory = new PrimedUuidFactory(Uuid::getFactory());
$uuidFactory->pushUuid('38b1020f-97eb-4561-8c95-7591caaebed3');
Uuid::setFactory($this->uuidFactory);
Hey @ramsey ,
I am trying to create a uuid that is predictable for a specific integration test where I am hitting a route in Laravel and I generate some thumbnails based on the created user uuid. I am struggling on figuring out how to create a Mockery object that will just return the Uuid::uuid4()->toString() method with a known fake value.
Here is what I have in my eloquent model in Larave User.phpl:
Then my controller is doing this when creating a user:
So my integration test is going something I want to just setup top in the test:
You can see the
tn-$size-uuid-baz-now.jpeg
that is the name of thumnbail that is generated after I create the user. I want in my test to set the output ofUuid:uuid4()
touuid-baz
so that my other tests will work when generating the names of the thumbnails for the user.I know this is long winded question but I am struggling to figure out how to swap in and control the output.
Have you ever played with Carbon? Check this out they have a testing aid: http://carbon.nesbot.com/docs/#api-testing
There is a way to set the
now()
method:Is there an easy way to do this for Uuid? If not, it's ok just going on a tangent here which is a nice to have here.