diff --git a/README.md b/README.md index 9caeba6..5bfad94 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 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 @@ - + 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..551df16 --- /dev/null +++ b/src/Testing/TemporalTestTime.php @@ -0,0 +1,52 @@ +service = TestService::create($temporalServerAddress); + } + + 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; + } + + 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..90b53d9 --- /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 () => ! TemporalTestTime::timeSkippingIsEnabled()); 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,