Skip to main content

Testing

Workflow::fake() swaps the execution manager for a recording wrapper. Workflows still run for real — through the real engine, nodes, and resume logic — but in Test mode: waits don't sleep, child workflows resolve inline, nothing touches a real queue. Every run is recorded so you can assert on it.

use Illuminate\Foundation\Testing\RefreshDatabase;
use Qanna\WorkflowEngine\Engine\Nodes\Action\LogNode;
use Qanna\WorkflowEngine\Engine\Triggers\ManualTrigger;
use Qanna\WorkflowEngine\Facades\Workflow;
use Qanna\WorkflowEngine\Models\Workflow as WorkflowModel;

class SignupTest extends TestCase
{
use RefreshDatabase;

public function test_signup_sends_a_welcome_email(): void
{
WorkflowModel::create([
'id' => 'send-welcome-email',
'name' => 'Send Welcome Email',
'trigger' => ['type' => ManualTrigger::type(), 'config' => []],
'nodes' => [
['id' => 'greet', 'type' => LogNode::type(), 'config' => ['message' => 'Welcome {{ trigger.email }}']],
],
'edges' => [
['from' => 'trigger', 'to' => 'greet', 'branch' => 'main'],
],
])->publish();

Workflow::fake();

$execution = Workflow::run('send-welcome-email', ['email' => 'ada@example.com']);

Workflow::assertRan('send-welcome-email');
Workflow::assertCompleted($execution->id);
}
}

Workflow::fake() still reads workflows from, and writes execution records to, the database — your tests need the package's migration (for example via RefreshDatabase). Workflow::run() only runs published workflows, so publish the ones your test creates, or use Workflow::runDraft().

Assertions​

AssertionPasses when
assertRan($workflowId)The workflow ran at least once via run() or runDraft().
assertNotRan($workflowId)It never ran.
assertNothingRan()No workflow ran.
assertDispatched($workflowId)It was started via dispatch().
assertResumed($executionId)That execution was resumed.
assertWorkflowRanTimes($workflowId, $n)It ran exactly $n times.
assertExecutionCount($n)$n distinct executions were recorded.
assertCompleted($executionId)Final status Succeeded.
assertSuspended($executionId)Final status Suspended.
assertFailed($executionId)Final status Failed.
assertExecutionStatus($executionId, ExecutionStatus::…)Exact status.
assertExecutionOutput($executionId, $expected)Output identical to $expected.

Accessors​

$fake = Workflow::fake();
// ... run some workflows ...

$fake->runs(); // WorkflowExecution[] — one per run() / runDraft() call
$fake->dispatches(); // one per dispatch() call
$fake->resumes(); // one per resume() call
$fake->executions(); // deduped by execution id, latest state
$fake->findExecution($executionId);

Testing suspend / resume​

Because child workflows and waits resolve inline in test mode, a full suspend → resume cycle runs in one test:

Workflow::fake();

$suspended = Workflow::run('needs-approval');
Workflow::assertSuspended($suspended->id);

$resumed = Workflow::resume($suspended->id, ['approved' => true]);
Workflow::assertCompleted($suspended->id);
$this->assertSame(['approved' => true], $resumed->output);

A child workflow started by a Call workflow node is recorded too, so Workflow::assertRan('the-child') works without extra wiring.

Registering a fixture node​

If your test needs a bespoke node (e.g. one that suspends on demand), register it on the registry in setUp():

$this->app->make(\Qanna\WorkflowEngine\Engine\NodeRegistry::class)
->register(MyFakeApprovalNode::class);