Common Patterns¶
Reusable patterns for FHIR testing with FHIR Frog.
Create → Read → Delete (CRUD Lifecycle)¶
The most common pattern: create a resource, test operations against it, clean up.
Setup:
* operation create Patient from patient as created
* assert response = created
* variable patientId = Patient.id from created
Test: "Read patient"
* operation read Patient/${patientId} as readResponse
* assert response = okay
Test: "Update patient"
* operation update Patient/${patientId} from patient-updated as updateResponse
* assert response = okay
Teardown:
* operation delete Patient/${patientId}
Extract and Reuse IDs¶
Always extract resource IDs in Setup so they're available in tests:
Setup:
* operation create Patient from patient as created
* variable patientId = Patient.id from created
* operation create Observation from obs as obsCreated
* variable obsId = Observation.id from obsCreated
Transaction Bundle Setup¶
Create related resources atomically before tests:
fixtures/related-resources.json
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:org-1",
"resource": { "resourceType": "Organization", "name": "Test Hospital" },
"request": { "method": "POST", "url": "Organization" }
},
{
"fullUrl": "urn:uuid:patient-1",
"resource": {
"resourceType": "Patient",
"managingOrganization": { "reference": "urn:uuid:org-1" }
},
"request": { "method": "POST", "url": "Patient" }
}
]
}
Conditional Create (Avoid Duplicates)¶
Use ifNoneExist for idempotent setup:
{
"request": {
"method": "POST",
"url": "Practitioner",
"ifNoneExist": "identifier=http://example.org/hpii|8003610833334085"
}
}
Search After Create¶
Verify a resource is findable after creation:
Setup:
* operation create Patient from patient as created
* variable patientId = Patient.id from created
Test: "Patient appears in search"
* operation search Patient ?_id=${patientId} as searchResponse
* assert response = okay
* assert Bundle.total = 1
* assert Bundle.entry[0].resource.id = "${patientId}"
State Machine Testing¶
For stateful resources (e.g. ServiceRequest status transitions):
TestPlan: ServiceRequestLifecycle
TestCase: CreateDraft
TestRun: TestScript/create-draft
TestCase: Activate
Dependency: CreateDraft
TestRun: TestScript/activate # draft → active
TestCase: Complete
Dependency: Activate
TestRun: TestScript/complete # active → completed
TestCase: RevokeAlternative
Dependency: CreateDraft
TestRun: TestScript/revoke # draft → revoked (parallel branch)
Positive + Negative Test Pairing¶
For every valid operation, add a corresponding invalid one:
Test: "Valid Medicare number accepted (201)"
* operation create Patient from valid-patient as ok
* assert response = created
Test: "Invalid Medicare number rejected (422)"
* operation create Patient from invalid-patient as bad
* assert responseCode = 422
Reusable Fixtures Library¶
Keep shared fixtures in a central location:
src/test/resources/fhir/
├── fixtures/
│ ├── patient-base.json # minimal Patient
│ ├── patient-with-medicare.json # Patient with Medicare ID
│ ├── practitioner-hpii.json # Practitioner with HPI-I
│ └── org-hpio.json # Organization with HPI-O
└── testscripts/
├── patient-crud.tsh
└── ...
Reference from any TestScript: Fixture: patient from "fixtures/patient-base.json"
Variable Propagation in TestPlans¶
Variables set in one TestCase are available in dependent TestCases:
Next Steps¶
- Best Practices — Broader testing principles
- Troubleshooting — Diagnose failures
- AU Core Example — Complete real-world example