Skip to main content

Testing

Workflows

You can execute workflows synchronously in your test environment and mock activities and child workflows to define expected behaviors and outputs without running the actual implementations.

use Workflow\ActivityStub;
use Workflow\Workflow;

class MyWorkflow extends Workflow
{
public function execute()
{
$result = yield ActivityStub::make(MyActivity::class);

return $result;
}
}

The above workflow can be tested by first calling WorkflowStub::fake() and then mocking the activity.

public function testWorkflow()
{
WorkflowStub::fake();

WorkflowStub::mock(MyActivity::class, 'result');

$workflow = WorkflowStub::make(MyWorkflow::class);
$workflow->start();

$this->assertSame($workflow->output(), 'result');
}

You can also provide a callback instead of a result value to WorkflowStub::mock().

The workflow $context along with any arguments for the current activity will also be passed to the callback.

public function testWorkflow()
{
WorkflowStub::fake();

WorkflowStub::mock(MyActivity::class, function ($context) {
return 'result';
});

$workflow = WorkflowStub::make(MyWorkflow::class);
$workflow->start();

$this->assertSame($workflow->output(), 'result');
}

You can assert which activities or child workflows were dispatched by using the assertDispatched, assertNotDispatched, and assertNothingDispatched methods:

WorkflowStub::assertDispatched(MyActivity::class);

// Assert the activity was dispatched twice...
WorkflowStub::assertDispatched(MyActivity::class, 2);

WorkflowStub::assertNotDispatched(MyActivity::class);

WorkflowStub::assertNothingDispatched();

You may pass a closure to the assertDispatched or assertNotDispatched methods in order to assert that an activity or child workflow was dispatched that passes a given "truth test". The arguments for the activity or child workflow will be passed to the callback.

WorkflowStub::assertDispatched(TestOtherActivity::class, function ($string) {
return $string === 'other';
});

Skipping Time

By manipulating the system time with $this->travel() or $this->travelTo(), you can simulate time-dependent workflows. This strategy allows you to test timeouts, delays, and other time-sensitive logic within your workflows.

use Workflow\ActivityStub;
use Workflow\Workflow;
use Workflow\WorkflowStub;

class MyTimerWorkflow extends Workflow
{
public function execute()
{
yield WorkflowStub::timer(60);

$result = yield ActivityStub::make(MyActivity::class);

return $result;
}
}

The above workflow waits 60 seconds before executing the activity. Using $this->travel() and $workflow->resume() allows us to skip this waiting period.

public function testTimeTravelWorkflow()
{
WorkflowStub::fake();

WorkflowStub::mock(MyActivity::class, 'result');

$workflow = WorkflowStub::make(MyTimerWorkflow::class);
$workflow->start();

$this->travel(120)->seconds();

$workflow->resume();

$this->assertSame($workflow->output(), 'result');
}

The helpers $this->travel() and $this->travelTo() methods use Carbon:setTestNow() under the hood.

Activities

Testing activities is similar to testing Laravel jobs. You manually create the activity and then call the handle() method.

$workflow = WorkflowStub::make(MyWorkflow::class);

$activity = new MyActivity(0, now()->toDateTimeString(), StoredWorkflow::findOrFail($workflow->id()));

$result = $activity->handle();

Note that we call the handle method and not the execute() method.