acts/time-mock-bundle
Symfony bundle for mocking and controlling time in tests and development. Freeze, fast-forward, and reset “now” to make time-dependent code deterministic, simplify assertions, and avoid flaky tests across DateTime/Clock usage.
Installation Add the package via Composer:
composer require acts/time-mock-bundle
Enable the bundle in config/bundles.php:
return [
// ...
Acts\TimeMockBundle\ActsTimeMockBundle::class => ['all' => true],
];
Basic Usage
Register a mock time provider in your test bootstrap (e.g., tests/TestCase.php):
use Acts\TimeMockBundle\TimeMock;
class TestCase extends \PHPUnit\Framework\TestCase
{
protected function setUp(): void
{
TimeMock::setTime('2023-01-01 12:00:00');
}
protected function tearDown(): void
{
TimeMock::reset();
}
}
First Use Case
Test time-sensitive logic (e.g., a User::isActive() method that checks last login):
public function testUserActivity()
{
$user = new User();
$user->lastLogin = '2023-01-01 11:00:00'; // Set via mock
$this->assertTrue($user->isActive()); // Logic now uses mocked time
}
Mocking Time Globally
Override time functions (now(), Carbon::now(), etc.) in tests:
TimeMock::setTime('2023-01-01 12:00:00');
$this->assertEquals('2023-01-01 12:00:00', now()->toDateTimeString());
Time Travel Increment time dynamically:
TimeMock::setTime('2023-01-01 12:00:00');
TimeMock::addHours(2); // Now returns 14:00
Integration with Carbon Works seamlessly with Laravel’s Carbon:
$mockedTime = Carbon::parse('2023-01-01 12:00:00');
TimeMock::setTime($mockedTime);
Scoped Mocking Useful for nested tests:
TimeMock::setTime('2023-01-01 12:00:00');
$this->testScopedTime(); // Uses mocked time
TimeMock::reset(); // Restore original time
Freezing Time Pause time for deterministic tests:
TimeMock::freeze();
// All time calls return the same value until unfrozen
TimeMock::unfreeze();
Custom Providers Extend functionality for specific needs:
TimeMock::setProvider(new CustomTimeProvider());
Global State
Mocking time affects all tests unless explicitly reset. Use tearDown() to avoid leaks:
protected function tearDown(): void
{
TimeMock::reset(); // Critical!
}
Time Zone Confusion
Ensure mocked time respects your app’s timezone (e.g., config/app.php):
TimeMock::setTime('2023-01-01 12:00:00', 'UTC'); // Explicit timezone
Static Method Dependencies
Some libraries (e.g., DateTime::createFromFormat()) may bypass the mock. Use Carbon or wrap calls:
$mocked = TimeMock::now(); // Use provided helper
Verify Mocked Time Log the current mocked time:
$this->assertEquals('2023-01-01 12:00:00', TimeMock::getTime());
Check for Resets
If tests fail unexpectedly, confirm TimeMock::reset() is called.
Custom Time Providers
Implement Acts\TimeMockBundle\Provider\TimeProviderInterface for bespoke logic:
class CustomProvider implements TimeProviderInterface
{
public function getTime(): \DateTimeInterface
{
return new \DateTime('now', new \DateTimeZone('America/New_York'));
}
}
Override Default Bindings
Replace Laravel’s now() binding in AppServiceProvider:
use Acts\TimeMockBundle\TimeMock;
public function register()
{
$this->app->singleton(\Illuminate\Contracts\Foundation\Application::class, function () {
$app = new Application();
$app->bind('now', function () {
return TimeMock::now();
});
return $app;
});
}
Mocking in CI/CD Ensure CI environments don’t interfere with mocked time by resetting early:
beforeApplication($app) {
TimeMock::reset();
}
How can I help you explore Laravel packages today?