Skip to content

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:

TestCase: Create
  # sets ${patientId}

TestCase: Search
  Dependency: Create
  # can use ${patientId}

Next Steps