Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/Chronos.php
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,38 @@ public static function hasTestNow(): bool
return static::$testNow !== null;
}

/**
* Temporarily sets "now" to the given value and executes the callback.
*
* After the callback is executed, the previous value of "now" is restored.
* This is useful for testing time-sensitive code without affecting other tests.
*
* ### Example:
*
* ```
* $result = Chronos::withTestNow('2023-06-15 12:00:00', function () {
* return Chronos::now()->format('Y-m-d');
* });
* // $result === '2023-06-15'
* ```
*
* @template T
* @param \Cake\Chronos\Chronos|string|null $testNow The instance to use as "now".
* @param callable(): T $callback The callback to execute.
* @return T The return value of the callback.
*/
public static function withTestNow(Chronos|string|null $testNow, callable $callback): mixed
{
$previous = static::getTestNow();
static::setTestNow($testNow);

try {
return $callback();
} finally {
static::setTestNow($previous);
}
}

/**
* Determine if there is just a time in the time string
*
Expand Down
86 changes: 86 additions & 0 deletions tests/TestCase/DateTime/TestingAidsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Cake\Chronos\ChronosDate;
use Cake\Chronos\Test\TestCase\TestCase;
use DateTimeZone;
use RuntimeException;

class TestingAidsTest extends TestCase
{
Expand Down Expand Up @@ -231,4 +232,89 @@ public function testSetTestNowSingular()

$this->assertSame($c, Chronos::getTestNow());
}

public function testWithTestNowSetsAndRestoresNull()
{
$this->assertNull(Chronos::getTestNow());

$result = Chronos::withTestNow('2023-06-15 12:00:00', function () {
$this->assertNotNull(Chronos::getTestNow());
$this->assertSame('2023-06-15', Chronos::now()->format('Y-m-d'));

return 'callback result';
});

$this->assertSame('callback result', $result);
$this->assertNull(Chronos::getTestNow());
}

public function testWithTestNowRestoresPreviousTestNow()
{
$original = new Chronos('2020-01-01 00:00:00');
Chronos::setTestNow($original);

Chronos::withTestNow('2023-06-15 12:00:00', function () {
$this->assertSame('2023-06-15', Chronos::now()->format('Y-m-d'));
});

$this->assertSame($original, Chronos::getTestNow());
$this->assertSame('2020-01-01', Chronos::now()->format('Y-m-d'));
}

public function testWithTestNowNested()
{
Chronos::setTestNow('2020-01-01 00:00:00');

Chronos::withTestNow('2021-06-15 00:00:00', function () {
$this->assertSame('2021-06-15', Chronos::now()->format('Y-m-d'));

Chronos::withTestNow('2022-12-25 00:00:00', function () {
$this->assertSame('2022-12-25', Chronos::now()->format('Y-m-d'));
});

$this->assertSame('2021-06-15', Chronos::now()->format('Y-m-d'));
});

$this->assertSame('2020-01-01', Chronos::now()->format('Y-m-d'));
}

public function testWithTestNowRestoresOnException()
{
$original = new Chronos('2020-01-01 00:00:00');
Chronos::setTestNow($original);

try {
Chronos::withTestNow('2023-06-15 12:00:00', function () {
throw new RuntimeException('Test exception');
});
$this->fail('Exception should have been thrown');
} catch (RuntimeException $e) {
$this->assertSame('Test exception', $e->getMessage());
}

$this->assertSame($original, Chronos::getTestNow());
}

public function testWithTestNowWithChronosInstance()
{
$testTime = new Chronos('2023-06-15 14:30:00');

$result = Chronos::withTestNow($testTime, function () {
return Chronos::now()->format('Y-m-d H:i:s');
});

$this->assertSame('2023-06-15 14:30:00', $result);
$this->assertNull(Chronos::getTestNow());
}

public function testWithTestNowWithNull()
{
Chronos::setTestNow('2020-01-01 00:00:00');

Chronos::withTestNow(null, function () {
$this->assertNull(Chronos::getTestNow());
});

$this->assertSame('2020-01-01', Chronos::now()->format('Y-m-d'));
}
}