Troubleshoot a Symfony ClockMock test
Review of known pitfalls to quickly debug a ClockMock test

10+ years experience as CTO at AssoConnect.
We are helping nonprofits change the world with an all-in-one software to save time on daily management.
Search for a command to run...
Review of known pitfalls to quickly debug a ClockMock test

10+ years experience as CTO at AssoConnect.
We are helping nonprofits change the world with an all-in-one software to save time on daily management.
No comments yet. Be the first to comment.
And some tips & tricks to modify dates with PHP

I recently deployed a new instance of Azure Database for MySQL Flexible Server.And I found out it uses a different SSL certificate than the legacy Single Server. This obviously broke my current Adminer setup with the login-ssl.php plugin so I decided...

Key points and strategy to migrate from a proprietary framework to Symfony, discussing routing, service injection, and database entities.

AssoConnect Technical Blog
4 posts
We help nonprofits change the world with an all-in-one software. We like curious and product-oriented developers always eager to improve their coding skills. Join us!
The Symfony ClockMock is an amazing yet tricky feature to mock date & time in your PHPUnit test suite.
You followed the documentation and yet date and time don't get mocked in your tests?
Let's review how it works and the common pitfalls we know of at Springly.
The ClockMock relies on PHP name resolution rules to define date & time functions in your app namespace when you run tests annotated @time-sensitive
namespace App\Tests\Something
use PHPUnit\Framework\TestCase;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* @group time-sensitive
*/
class MyTest extends TestCase
{
public function testSomething()
{
$stopwatch = new Stopwatch();
$stopwatch->start('event_name');
sleep(10);
$duration = $stopwatch->stop('event_name')->getDuration();
self::assertEquals(10000, $duration);
}
}
The following functions are defined with the previous code block:
App\Tests\Something\time() & App\Something\time()App\Tests\Something\microtime() & App\Something\microtime()App\Tests\Something\sleep() & App\Something\sleep()App\Tests\Something\usleep() & App\Something\usleep()App\Tests\Something\date() & App\Something\date()App\Tests\Something\gmdate() & App\Something\gmdate()For the most curious, this is done in Symfony\Bridge\PhpUnit\ClockMock with an eval() call.
ClockMock only works if you run your test suite with the vendor/bin/simple-phpunit binary.
Don't use Composer vendor/autoload.php or the base vendor/bin/phpunit binary when running tests. For instance, the right setup with Docker & PHPStorm is

Alternatively, you can also use this configuration to explicitly set up the right listener:
<!-- phpunit.xml.dist -->
<!-- ... -->
<listeners>
<listener class="\Symfony\Bridge\PhpUnit\SymfonyTestsListener"/>
</listeners>
Only the following functions are mockable:
time()microtime()sleep()usleep()date()gmdate()The DateTime class is not mockable nor other PHP classes.
However, you can use this as a workaround:
$real = new DateTime('now');
$mocked = new DateTime('@' . time());
Time-based function mocking follows the PHP namespace resolutions rules so "fully qualified function calls" (e.g \time()) cannot be mocked.
Following the first example, the PHPUnit bridge will mock time functions in these namespaces:
App\SomethingApp\Tests\SomethingCheck that your time functions calls are actually done within one of these mocked namespace or call ClockMock::register(MyClass::class) beforehand.
Newest versions of PHP cache name resolution results to improve performance as detailed in the following bug reports:
It means that the following test suite won't work as you expect.
namespace App\Tests\Something
use PHPUnit\Framework\TestCase;
use Symfony\Component\Stopwatch\Stopwatch;
class MyTest extends TestCase
{
public function testSomething()
{
$now = time();
self::assertTrue($now > 0);
ClockMock::register(MyTest::class);
ClockMock::withClockMock(0);
// This will fail as the namespace resolution result was cached
// by the previous call to time()
self::assertEquals(0, time());
}
}
You can either use a bootstrap.php or a custom PHPUnit configuration to work around it for common used classes in your codebase (like a base Doctrine entity trait to set createdAt & updatedAt properties).
<!-- phpunit.xml.dist -->
<!-- ... -->
<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener">
<arguments>
<array>
<element key="time-sensitive">
<string>App\Tests\Something</string>
</element>
</array>
</arguments>
</listener>
</listeners>
or a bootstrap file:
# bootstrap.php to setup in phpunit.xml
ClockMock::register('App\\Something\\');
⚠️ Use the namespace and not the fully qualified class name.
If your class is App\Something\MyClass then use App\Something in the example above and not App\Something\MyClass.
Happy testing!