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:
- The definition loads successfully
- Conditions behave as expected
- Expected recommendations are produced
- Invalid tool access fails clearly
- 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
nextreferences - 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 typecheckLightweight 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
| Failure | Typical cause | Fix |
|---|---|---|
| Definition fails to load | Missing id, name, version, or steps | Validate the shape before testing |
| Condition never matches | Wrong path in context.data or prior-step reference | Confirm names like biomarker.TSH or check-tsh.result |
run_tool fails | Tool not present in context.tools | Add the tool ID to the allowlist |
| Recommendation missing | recommend_supplement has no supplement param | Provide supplement in action params |