Skip to content
Merged
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
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
</source>
<php>
<server name="TEMPORAL_TESTING_DEBUG" value="false"/>
<server name="TEMPORAL_TESTING_SERVER_TIME_SKIPPING" value="false"/>
<server name="TEMPORAL_TESTING_SERVER_TIME_SKIPPING" value="true"/>
</php>
</phpunit>
2 changes: 0 additions & 2 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -32,7 +31,6 @@
AddParamBasedOnParentClassMethodRector::class,
ClosureToArrowFunctionRector::class,
FlipTypeControlToUseExclusiveTypeRector::class,
FunctionLikeToFirstClassCallableRector::class,
IfIssetToCoalescingRector::class,
RemoveNullPropertyInitializationRector::class,
ReturnTypeFromReturnDirectArrayRector::class,
Expand Down
5 changes: 5 additions & 0 deletions src/LaravelTemporalServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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'));
});
}
}
2 changes: 1 addition & 1 deletion src/Testing/LocalTemporalServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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:',
Expand Down
52 changes: 52 additions & 0 deletions src/Testing/TemporalTestTime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Keepsuit\LaravelTemporal\Testing;

use Carbon\Carbon;
use Illuminate\Container\Container;
use Temporal\Testing\TestService;

final class TemporalTestTime
{
protected TestService $service;

public function __construct(string $temporalServerAddress)
{
$this->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();
}
}
2 changes: 1 addition & 1 deletion src/Testing/TemporalTestingWorker.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
24 changes: 24 additions & 0 deletions src/Testing/WithoutTimeSkipping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Keepsuit\LaravelTemporal\Testing;

trait WithoutTimeSkipping
{
protected function setUpWithoutTimeSkipping(): void
{
if (! TemporalTestTime::timeSkippingIsEnabled()) {
return;
}

TemporalTestTime::lock();
}

protected function tearDownWithoutTimeSkipping(): void
{
if (! TemporalTestTime::timeSkippingIsEnabled()) {
return;
}

TemporalTestTime::unlock();
}
}
20 changes: 20 additions & 0 deletions tests/Fixtures/WorkflowDiscovery/Workflows/AsyncWorkflow.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Keepsuit\LaravelTemporal\Tests\Fixtures\WorkflowDiscovery\Workflows;

use Temporal\Workflow;
use Temporal\Workflow\WorkflowInterface;
use Temporal\Workflow\WorkflowMethod;

#[WorkflowInterface]
class AsyncWorkflow
{
#[WorkflowMethod(name: 'async')]
#[Workflow\ReturnType('string')]
public function async(): \Generator
{
yield Workflow::timer(3);

return 'ok';
}
}
25 changes: 25 additions & 0 deletions tests/Integrations/Temporal/AsyncWorkflowTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

use Carbon\CarbonInterval;
use Keepsuit\LaravelTemporal\Facade\Temporal;
use Keepsuit\LaravelTemporal\Testing\TemporalTestTime;
use Keepsuit\LaravelTemporal\Testing\WithoutTimeSkipping;
use Keepsuit\LaravelTemporal\Testing\WithTemporal;
use Keepsuit\LaravelTemporal\Tests\Fixtures\WorkflowDiscovery\Workflows\AsyncWorkflow;
use Temporal\Common\RetryOptions;

uses(WithTemporal::class);
uses(WithoutTimeSkipping::class);

test('time skipping', function () {
$workflow = Temporal::newWorkflow()
->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());
2 changes: 2 additions & 0 deletions tests/Unit/Discovery/WorkflowDiscoveryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,6 +16,7 @@

expect($workflows)->toBe([
ActivityOptionsWorkflow::class,
AsyncWorkflow::class,
DemoWorkflow::class,
DemoWorkflowInterfaceOnly::class,
DemoWorkflowWithoutInterface::class,
Expand Down