Skip to Content

Testing Workflows

Testing matters because the workflow engine is declarative: the workflow definition is data, but the behavior is still code-driven. The easiest way to keep workflows safe is to test both the definition and the execution path.

What to test

For every workflow, verify:

  1. The definition loads successfully
  2. Conditions behave as expected
  3. Expected recommendations are produced
  4. Invalid tool access fails clearly
  5. Edge cases skip safely instead of crashing

Unit-test the definition

Use WorkflowEngine.load() in a test to validate the structure:

import { afterEach, describe, expect, it } from 'vitest'; import { WorkflowEngine } from '@loop/workflow-engine'; afterEach(() => { WorkflowEngine.clear(); }); describe('thyroid workflow', () => { it('loads a valid workflow definition', () => { const workflow = WorkflowEngine.load(` id: thyroid-workflow-v1 name: Thyroid Assessment version: 1.0.0 steps: - id: check-tsh type: check_biomarker action: type: check_value params: biomarker: TSH threshold: 2.5 operator: ">" `); expect(workflow.definition.id).toBe('thyroid-workflow-v1'); expect(workflow.definition.steps).toHaveLength(1); }); });

Test execution behavior

Run the workflow with known input and assert on recommendations:

import { describe, expect, it } from 'vitest'; import { WorkflowEngine } from '@loop/workflow-engine'; describe('thyroid workflow execution', () => { it('recommends selenium when TSH is high', async () => { const workflow = WorkflowEngine.load(` id: thyroid-workflow-v1 name: Thyroid Assessment version: 1.0.0 steps: - id: check-tsh type: check_biomarker action: type: check_value params: biomarker: TSH threshold: 2.5 operator: ">" - id: recommend-selenium type: recommend_supplement condition: "check-tsh.result === true" action: type: recommend params: supplement: Selenium dosage: 200mcg `); const result = await workflow.execute({ patientId: 'patient-42', data: { biomarker: { TSH: 4.2 }, }, }); expect(result.success).toBe(true); expect(result.recommendations).toHaveLength(1); expect(result.recommendations[0]?.details.supplement).toBe('Selenium'); }); });

Test skipped branches

Conditions should also be tested when they do not match:

const result = await workflow.execute({ patientId: 'patient-42', data: { biomarker: { TSH: 1.8 }, }, }); expect(result.success).toBe(true); expect(result.recommendations).toHaveLength(0); expect(result.stepResults['recommend-selenium']?.result).toBe('skipped');

Test tool availability

The run_tool step checks the tool allowlist in context.tools. If a tool is unavailable, the step fails clearly.

const workflow = WorkflowEngine.load(` id: tool-test-v1 name: Tool Test version: 1.0.0 steps: - id: call-tool type: run_tool action: type: tool params: toolId: lookup-peptide `); const result = await workflow.execute({ patientId: 'patient-1', tools: ['product-search'], }); expect(result.success).toBe(false); expect(result.errors[0]?.stepId).toBe('call-tool');

Reuse the package test patterns

The repository already tests:

  • workflow loading from YAML and JSON
  • duplicate step validation
  • unknown next references
  • condition operators
  • contraindication checks
  • notification behavior
  • tool access failures

Mirror those patterns when you add new workflows.

Run the existing test suite

pnpm --filter @loop/workflow-engine test pnpm --filter @loop/workflow-engine typecheck

Lightweight smoke test script

For local authoring, a small script is often enough:

import { WorkflowEngine } from '@loop/workflow-engine'; const workflow = WorkflowEngine.load(` id: smoke-test name: Smoke Test version: 1.0.0 steps: - id: check-crp type: check_biomarker action: type: check_value params: biomarker: CRP threshold: 3 operator: ">" `); const result = await workflow.execute({ patientId: 'patient-99', data: { biomarker: { CRP: 4.4 } }, }); console.log(JSON.stringify(result, null, 2));

Common failure cases

FailureTypical causeFix
Definition fails to loadMissing id, name, version, or stepsValidate the shape before testing
Condition never matchesWrong path in context.data or prior-step referenceConfirm names like biomarker.TSH or check-tsh.result
run_tool failsTool not present in context.toolsAdd the tool ID to the allowlist
Recommendation missingrecommend_supplement has no supplement paramProvide supplement in action params

Next steps