From bff49dc0fa48169269fc3ff103a11dbc3d2a4d50 Mon Sep 17 00:00:00 2001 From: Fabio Capucci Date: Sun, 21 Dec 2025 16:39:25 +0100 Subject: [PATCH 1/5] time testing utilities --- rector.php | 2 - src/LaravelTemporalServiceProvider.php | 5 ++ src/Testing/LocalTemporalServer.php | 2 +- src/Testing/TemporalTestTime.php | 47 +++++++++++++++++++ src/Testing/TemporalTestingWorker.php | 2 +- src/Testing/WithoutTimeSkipping.php | 24 ++++++++++ .../Workflows/AsyncWorkflow.php | 20 ++++++++ .../Temporal/AsyncWorkflowTest.php | 25 ++++++++++ .../Unit/Discovery/WorkflowDiscoveryTest.php | 2 + 9 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 src/Testing/TemporalTestTime.php create mode 100644 src/Testing/WithoutTimeSkipping.php create mode 100644 tests/Fixtures/WorkflowDiscovery/Workflows/AsyncWorkflow.php create mode 100644 tests/Integrations/Temporal/AsyncWorkflowTest.php diff --git a/rector.php b/rector.php index b23b037..b1b805d 100644 --- a/rector.php +++ b/rector.php @@ -3,7 +3,6 @@ declare(strict_types=1); use Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector; -use Rector\CodingStyle\Rector\FunctionLike\FunctionLikeToFirstClassCallableRector; use Rector\Config\RectorConfig; use Rector\DeadCode\Rector\PropertyProperty\RemoveNullPropertyInitializationRector; use Rector\Php70\Rector\StmtsAwareInterface\IfIssetToCoalescingRector; @@ -32,7 +31,6 @@ AddParamBasedOnParentClassMethodRector::class, ClosureToArrowFunctionRector::class, FlipTypeControlToUseExclusiveTypeRector::class, - FunctionLikeToFirstClassCallableRector::class, IfIssetToCoalescingRector::class, RemoveNullPropertyInitializationRector::class, ReturnTypeFromReturnDirectArrayRector::class, diff --git a/src/LaravelTemporalServiceProvider.php b/src/LaravelTemporalServiceProvider.php index fa44942..1956df5 100644 --- a/src/LaravelTemporalServiceProvider.php +++ b/src/LaravelTemporalServiceProvider.php @@ -17,6 +17,7 @@ use Keepsuit\LaravelTemporal\Support\ServerStateFile; use Keepsuit\LaravelTemporal\Testing\TemporalMocker; use Keepsuit\LaravelTemporal\Testing\TemporalMockerCache; +use Keepsuit\LaravelTemporal\Testing\TemporalTestTime; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; use Temporal\Client\ClientOptions; @@ -132,5 +133,9 @@ protected function setupTestingEnvironment(): void $this->app->singleton(TemporalMocker::class, fn (Application $app) => new TemporalMocker( cache: TemporalMockerCache::create() )); + + $this->app->singleton(TemporalTestTime::class, function (): TemporalTestTime { + return new TemporalTestTime(config('temporal.address')); + }); } } diff --git a/src/Testing/LocalTemporalServer.php b/src/Testing/LocalTemporalServer.php index a8666e1..9c5329d 100644 --- a/src/Testing/LocalTemporalServer.php +++ b/src/Testing/LocalTemporalServer.php @@ -144,7 +144,7 @@ protected function startServer(string $binaryPath, ?int $port): void try { $serverStarted = $this->temporalServerProcess->waitUntil( - fn ($type, $output) => Str::contains((string) $output, [ + fn ($type, $output) => Str::contains($output, [ 'http server started', 'Temporal server is running', 'Temporal server:', diff --git a/src/Testing/TemporalTestTime.php b/src/Testing/TemporalTestTime.php new file mode 100644 index 0000000..61e237f --- /dev/null +++ b/src/Testing/TemporalTestTime.php @@ -0,0 +1,47 @@ +service = TestService::create($temporalServerAddress); + } + + protected static function instance(): TemporalTestTime + { + return Container::getInstance()->make(TemporalTestTime::class); + } + + public static function testService(): TestService + { + return self::instance()->service; + } + + public static function lock(): void + { + TemporalTestTime::instance()->service->lockTimeSkipping(); + } + + public static function unlock(): void + { + TemporalTestTime::instance()->service->unlockTimeSkipping(); + } + + public static function sleep(int $seconds): void + { + TemporalTestTime::instance()->service->unlockTimeSkippingWithSleep($seconds); + } + + public static function now(): Carbon + { + return TemporalTestTime::instance()->service->getCurrentTime(); + } +} diff --git a/src/Testing/TemporalTestingWorker.php b/src/Testing/TemporalTestingWorker.php index 4a08bc2..659dbe9 100644 --- a/src/Testing/TemporalTestingWorker.php +++ b/src/Testing/TemporalTestingWorker.php @@ -108,7 +108,7 @@ protected function startTemporalWorker(): void try { $roadRunnerStarted = $this->roadRunnerProcess->waitUntil( - fn ($type, $output) => Str::contains((string) $output, 'RoadRunner server started') + fn ($type, $output) => Str::contains($output, 'RoadRunner server started') ); } catch (\Throwable) { $roadRunnerStarted = false; diff --git a/src/Testing/WithoutTimeSkipping.php b/src/Testing/WithoutTimeSkipping.php new file mode 100644 index 0000000..126e9ba --- /dev/null +++ b/src/Testing/WithoutTimeSkipping.php @@ -0,0 +1,24 @@ +withWorkflowRunTimeout(CarbonInterval::seconds(5)) + ->withRetryOptions(RetryOptions::new()->withMaximumAttempts(2)) + ->build(AsyncWorkflow::class); + + $run = Temporal::workflowClient()->start($workflow); + + TemporalTestTime::sleep(3); + + expect($run->getResult())->toBe('ok'); +})->skip(fn () => ! config('temporal.testing.time_skipping')); diff --git a/tests/Unit/Discovery/WorkflowDiscoveryTest.php b/tests/Unit/Discovery/WorkflowDiscoveryTest.php index 8f0742f..309bb50 100644 --- a/tests/Unit/Discovery/WorkflowDiscoveryTest.php +++ b/tests/Unit/Discovery/WorkflowDiscoveryTest.php @@ -2,6 +2,7 @@ use Keepsuit\LaravelTemporal\Support\DiscoverWorkflows; use Keepsuit\LaravelTemporal\Tests\Fixtures\WorkflowDiscovery\Workflows\ActivityOptionsWorkflow; +use Keepsuit\LaravelTemporal\Tests\Fixtures\WorkflowDiscovery\Workflows\AsyncWorkflow; use Keepsuit\LaravelTemporal\Tests\Fixtures\WorkflowDiscovery\Workflows\DemoWorkflow; use Keepsuit\LaravelTemporal\Tests\Fixtures\WorkflowDiscovery\Workflows\DemoWorkflowInterfaceOnly; use Keepsuit\LaravelTemporal\Tests\Fixtures\WorkflowDiscovery\Workflows\DemoWorkflowWithoutInterface; @@ -15,6 +16,7 @@ expect($workflows)->toBe([ ActivityOptionsWorkflow::class, + AsyncWorkflow::class, DemoWorkflow::class, DemoWorkflowInterfaceOnly::class, DemoWorkflowWithoutInterface::class, From 52326f1d6556c6099d307405b5bb6d9ca1d1e0a5 Mon Sep 17 00:00:00 2001 From: Fabio Capucci Date: Sun, 21 Dec 2025 16:42:48 +0100 Subject: [PATCH 2/5] enabled time skipping for tests --- phpunit.xml.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9c14de7..cf92b4b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -23,6 +23,6 @@ - + From 7c625e017a249d6061ecb34b08942122d8c00290 Mon Sep 17 00:00:00 2001 From: Fabio Capucci Date: Sun, 21 Dec 2025 17:43:58 +0100 Subject: [PATCH 3/5] test time example --- README.md | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9caeba6..0c4566d 100644 --- a/README.md +++ b/README.md @@ -351,10 +351,10 @@ class AppServiceProvider extends ServiceProvider In order to test workflows end-to-end, you need a temporal server running. This package provides two options to run a temporal server for testing purposes: -- Run `temporal:server` command, which will start a temporal testing server and use the `WithTemporalWorker` trait which will start a test worker +- Run `temporal:server` command, which will start a temporal testing server and use the `Keepsuit\LaravelTemporal\Testing\WithTemporalWorker` trait which will start a test worker - Use the `WithTemporal` trait, which will start a temporal testing server and the test worker when running test and stop it on finish -> When using `WithTemporal` trait, you can set `TEMPORAL_TESTING_SERVER` env variable to `false` +> When using `Keepsuit\LaravelTemporal\Testing\WithTemporal` trait, you can set `TEMPORAL_TESTING_SERVER` env variable to `false` > to disable the testing server and run only the worker. ### Time skipping @@ -363,7 +363,38 @@ The default temporal server implementation is the dev server included in the tem In order to enable time skipping, you must: - Run the `temporal:server` command with the `--enable-time-skipping` flag. -- Set `TEMPORAL_TESTING_SERVER_TIME_SKIPPING` env variable to `true` when using `WithTemporal` trait. +- Set `TEMPORAL_TESTING_SERVER_TIME_SKIPPING` env variable to `true` when using `Keepsuit\LaravelTemporal\Testing\WithTemporal` trait. + +When time skipping is enabled, the server doesn't wait for timers and continues immediately. +In order to control time behavior in your tests, you can add the `Keepsuit\LaravelTemporal\Testing\WithoutTimeSkipping` trait to your test class +and control time skipping with `Keepsuit\LaravelTemporal\Testing\TemporalTestTime` methods. + +```php +use Keepsuit\LaravelTemporal\Facade\Temporal; +use Keepsuit\LaravelTemporal\Testing\TemporalTestTime; +use Keepsuit\LaravelTemporal\Testing\WithoutTimeSkipping; +use Keepsuit\LaravelTemporal\Testing\WithTemporal; +use Tests\TestCase; + +class YourWorkflowTest extends TestCase +{ + use WithTemporal; + use WithoutTimeSkipping; + + public function test_workflow_with_timers() + { + $workflow = Temporal::newWorkflow() + ->build(YourWorkflowInterface::class); + + $run = Temporal::workflowClient()->start($workflow); + + // Advance time by 5 minutes + TemporalTestTime::sleep(5 * 60); + + // Your assertions... + } +} +``` ### Mocking workflows From 46a21e9af953ddeff34fd434c3e86235145d1ccf Mon Sep 17 00:00:00 2001 From: Fabio Capucci Date: Sun, 21 Dec 2025 17:56:50 +0100 Subject: [PATCH 4/5] added method to check time skipping status --- src/Testing/TemporalTestTime.php | 5 +++++ src/Testing/WithoutTimeSkipping.php | 4 ++-- tests/Integrations/Temporal/AsyncWorkflowTest.php | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Testing/TemporalTestTime.php b/src/Testing/TemporalTestTime.php index 61e237f..551df16 100644 --- a/src/Testing/TemporalTestTime.php +++ b/src/Testing/TemporalTestTime.php @@ -20,6 +20,11 @@ protected static function instance(): TemporalTestTime return Container::getInstance()->make(TemporalTestTime::class); } + public static function timeSkippingIsEnabled(): bool + { + return config()->boolean('temporal.testing.time_skipping', false); + } + public static function testService(): TestService { return self::instance()->service; diff --git a/src/Testing/WithoutTimeSkipping.php b/src/Testing/WithoutTimeSkipping.php index 126e9ba..90b53d9 100644 --- a/src/Testing/WithoutTimeSkipping.php +++ b/src/Testing/WithoutTimeSkipping.php @@ -6,7 +6,7 @@ trait WithoutTimeSkipping { protected function setUpWithoutTimeSkipping(): void { - if (! config('temporal.testing.time_skipping')) { + if (! TemporalTestTime::timeSkippingIsEnabled()) { return; } @@ -15,7 +15,7 @@ protected function setUpWithoutTimeSkipping(): void protected function tearDownWithoutTimeSkipping(): void { - if (! config('temporal.testing.time_skipping')) { + if (! TemporalTestTime::timeSkippingIsEnabled()) { return; } diff --git a/tests/Integrations/Temporal/AsyncWorkflowTest.php b/tests/Integrations/Temporal/AsyncWorkflowTest.php index 1f27d4d..653ab06 100644 --- a/tests/Integrations/Temporal/AsyncWorkflowTest.php +++ b/tests/Integrations/Temporal/AsyncWorkflowTest.php @@ -22,4 +22,4 @@ TemporalTestTime::sleep(3); expect($run->getResult())->toBe('ok'); -})->skip(fn () => ! config('temporal.testing.time_skipping')); +})->skip(fn () => ! TemporalTestTime::timeSkippingIsEnabled()); From 66e509ea4a11523caa05a7ef709e56327ba0a4e2 Mon Sep 17 00:00:00 2001 From: Fabio Capucci Date: Sun, 21 Dec 2025 18:05:08 +0100 Subject: [PATCH 5/5] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0c4566d..5bfad94 100644 --- a/README.md +++ b/README.md @@ -383,15 +383,15 @@ class YourWorkflowTest extends TestCase public function test_workflow_with_timers() { - $workflow = Temporal::newWorkflow() + $workflow = Temporal::newWorkflow() ->build(YourWorkflowInterface::class); - $run = Temporal::workflowClient()->start($workflow); - - // Advance time by 5 minutes - TemporalTestTime::sleep(5 * 60); - - // Your assertions... + $run = Temporal::workflowClient()->start($workflow); + + // Advance time by 5 minutes + TemporalTestTime::sleep(5 * 60); + + // Your assertions... } } ```