Testing Workflows
Alpha notice: During alpha, package and demo tests are often more current than prose docs. When in doubt, read
dbflow-demointegration tests and Core validation classes.
Workflow logic touches concurrency, branching, assignments, and audit logs. PHPUnit coverage is the safest way to ship DBFlow in a real Laravel app — especially while APIs are still evolving.
What to test
| Behaviour | Why it matters |
|---|---|
| Workflow starts | Definition resolves; instance row is created |
| Duplicate active workflow blocked | active_key prevents double submit |
| Pending task created | Approval nodes surface assignable work |
| Approve advances graph | Transitions fire; next node or completion |
| Reject applies strategy | RejectStrategy routing matches product rules |
| Hooks update business state | Host columns stay aligned with instance status |
| Logs written | dbflow_workflow_logs captures audit events |
Validate definitions first
Catch malformed graphs before runtime tests:
use DbflowLabs\Core\Validation\WorkflowDefinitionValidator;
app(WorkflowDefinitionValidator::class)->validateOrFail($definition);
Run this against provider output, seeder arrays, and exported JSON in CI.
Core test traits
Host apps and dbflow-demo compose PHPUnit traits from dbflowlabs/core test support:
| Trait | Namespace |
|---|---|
BuildsMinimalPublishedWorkflow |
DbflowLabs\Core\Tests\Concerns\BuildsMinimalPublishedWorkflow |
LoadsBlueprintFixtures |
DbflowLabs\Core\Tests\Concerns\LoadsBlueprintFixtures |
RegistersEngineTestResources |
DbflowLabs\Core\Tests\Concerns\RegistersEngineTestResources |
Mirror usage from dbflow-demo when wiring your own suite. Register providers and run SyncWorkflowDefinitions in test setUp() before calling DBFlow::start().
Example: start and duplicate guard
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Exceptions\WorkflowAlreadyRunningException;
public function test_start_creates_running_instance(): void
{
$dispute = RefundDispute::factory()->create();
DBFlow::start('refund_dispute_approval', $dispute, $this->user);
$this->assertTrue($dispute->hasRunningWorkflow('refund_dispute_approval'));
}
public function test_duplicate_start_is_blocked(): void
{
$dispute = RefundDispute::factory()->create();
DBFlow::start('refund_dispute_approval', $dispute, $this->user);
$this->expectException(WorkflowAlreadyRunningException::class);
DBFlow::start('refund_dispute_approval', $dispute, $this->user);
}
Example: approve advances the graph
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Enums\WorkflowTaskStatus;
public function test_support_lead_approval_creates_next_task(): void
{
$dispute = $this->startRefundDisputeUnderReview();
$task = $dispute->runningWorkflowInstance('refund_dispute_approval')
?->tasks()
->where('status', WorkflowTaskStatus::Pending)
->first();
DBFlow::approve($task, $this->supportLead, 'Approved.');
$this->assertDatabaseHas('dbflow_workflow_logs', [
'workflow_instance_id' => $task->workflow_instance_id,
'event' => 'task_approved',
]);
}
Adapt factory helpers to your app. The dbflow-demo project centralises bootstrapping in integration test base classes.
Example: reject strategy
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Enums\RejectStrategy;
public function test_reject_with_previous_node_returns_to_prior_approval(): void
{
$task = $this->pendingFinanceTask();
DBFlow::reject(
$task,
$this->financeUser,
'Need manager re-approval.',
RejectStrategy::PreviousNode,
);
$instance = $task->workflowInstance()->fresh();
$this->assertSame('procurement_manager_review', $instance->current_node_key);
}
Example: hooks and host status
public function test_approval_hook_sets_host_status(): void
{
$request = $this->approveProcurementRequestThroughWorkflow();
$this->assertSame(
ProcurementRequestStatus::Approved,
$request->fresh()->status,
);
}
Register real hook classes in test setUp() the same way as production service providers.
Demo integration tests (reference)
Study these files in dbflow-demo:
| Test | Coverage |
|---|---|
tests/Feature/RefundDisputeDbflowIntegrationTest.php |
End-to-end refund approval |
tests/Feature/RefundDisputeAmountBranchingTest.php |
refund_amount >= 500 condition branches |
tests/Feature/ProcurementRequestDbflowIntegrationTest.php |
Procurement flow and hook mapping |
They demonstrate published definitions, actor users, task resolution, and log assertions against real tables (dbflow_workflow_instances, dbflow_workflow_tasks, dbflow_workflow_logs).
Practical tips
- Freeze workflow keys — use constants (
RefundDisputeWorkflow::KEY) in tests and app code. - Seed or publish in setUp — instances need an active published version in
dbflow_workflow_versions(useSyncWorkflowDefinitionsorBuildsMinimalPublishedWorkflow). - Use actingAs() — pass the same user model class configured in
config('dbflow.auth.model'). - Assert logs, not only status — log rows prove the runtime path, not just final columns.
- Run package tests on upgrade — after bumping
dbflowlabs/core, runcomposer testin the package checkout or your CI matrix.
What's next
- Host Integration → — deploy sync and assignee wiring
- First Workflow → — hands-on setup walkthrough
- Conditions → — branch testing notes
- Reject Strategies → — strategy matrix
- Workflow Hooks → — hook side effects to assert
- Refund Approval → — demo file map