Best Practices¶
Guidelines for effective FHIR testing with FHIR Frog.
TestScript Design¶
Keep Tests Focused¶
Single Responsibility
Each TestScript should test one specific behavior or requirement.
Good:
TestScript: PatientCreate
Test: "Create patient with valid data"
* operation create Patient from validPatient
* assert responseCode = 201
Avoid:
TestScript: PatientEverything
Test: "Create, read, update, delete, search, validate..."
# Too many responsibilities
Use Meaningful Names¶
# Good
TestScript: PatientMedicareNumberValidation
TestScript: PractitionerHPIISearch
# Avoid
TestScript: Test1
TestScript: PatientTest
Document Test Intent¶
TestScript: PatientIHIValidation
Title: "Validate Patient with IHI identifier"
Description: """
Verifies that a Patient resource with a valid IHI
(Individual Healthcare Identifier) can be created
and retrieved successfully.
"""
Test Organization¶
Group Related Tests¶
Use TestPlans to organize:
TestPlan: AUCorePatientSuite
- PatientCreate
- PatientRead
- PatientUpdate
- PatientDelete
- PatientSearch
Separate Positive and Negative Tests¶
tests/
patient/
positive/
create-valid.tsh
search-by-identifier.tsh
negative/
create-invalid-gender.tsh
create-missing-name.tsh
Use Fixtures Effectively¶
fixtures/
patient/
valid-patient.json
patient-with-medicare.json
patient-with-ihi.json
invalid/
patient-invalid-gender.json
patient-missing-name.json
Variable Management¶
Extract IDs Early¶
Test: "Create and read patient"
* operation create Patient from patient as createResponse
* assert responseCode = 201
* variable patientId = Patient.id from createResponse
* operation read Patient/${patientId}
* assert responseCode = 200
Use Descriptive Variable Names¶
# Good
* variable patientId = Patient.id
* variable medicareNumber = Patient.identifier.where(system='...').value
# Avoid
* variable id1 = Patient.id
* variable x = Patient.identifier.value
Validate Before Using¶
* operation create Patient from patient as response
* assert expression = "Patient.id.exists()"
* variable patientId = Patient.id from response
Assertion Strategies¶
Test Multiple Aspects¶
Test: "Create patient"
* operation create Patient from patient
* assert responseCode = 201
* assert resource = Patient
* assert expression = "Patient.id.exists()"
* assert expression = "Patient.name.exists()"
Use FHIRPath for Complex Checks¶
# Check identifier system
* assert expression = "Patient.identifier.where(system='http://ns.electronichealth.net.au/id/medicare-number').exists()"
# Validate reference format
* assert expression = "Observation.subject.reference.startsWith('Patient/')"
Provide Clear Messages¶
* assert responseCode = 201
description = "Patient creation must return 201 Created"
* assert expression = "Patient.name.family.exists()"
description = "Patient must have a family name"
Error Handling¶
Clean Up on Failure¶
Setup:
* operation create Patient from patient as setupResponse
* variable patientId = Patient.id from setupResponse
Test: "Update patient"
* operation update Patient/${patientId} from updatedPatient
* assert responseCode = 200
Teardown:
* operation delete Patient/${patientId}
# Always runs, even if test fails
Use Warning-Only for Optional Checks¶
* assert expression = "Patient.telecom.where(system='email').exists()"
warningOnly = true
description = "Email is recommended but not required"
Performance¶
Minimize Server Calls¶
# Good - Single transaction
* operation transaction from patientBundle
# Avoid - Multiple calls
* operation create Patient from patient1
* operation create Patient from patient2
* operation create Patient from patient3
Reuse Setup Data¶
Setup:
* operation create Patient from sharedPatient as setupResponse
* variable sharedPatientId = Patient.id from setupResponse
Test: "Test 1"
* operation read Patient/${sharedPatientId}
Test: "Test 2"
* operation read Patient/${sharedPatientId}
CI/CD Integration¶
Fast Feedback¶
# Run critical tests first
test:critical:
script:
- mvn test -Dtest=CriticalTests
test:full:
script:
- mvn test
needs: [test:critical]
Parallel Execution¶
test:patient:
script:
- mvn test -Dtest=PatientTests
test:practitioner:
script:
- mvn test -Dtest=PractitionerTests
Store Reports¶
Testcontainers¶
Use Specific Versions¶
@Container
static GenericContainer<?> fhirServer = new GenericContainer<>("hapiproject/hapi:v6.4.0")
.withExposedPorts(8080);
Wait for Readiness¶
@Container
static GenericContainer<?> fhirServer = new GenericContainer<>("hapiproject/hapi:latest")
.withExposedPorts(8080)
.waitingFor(Wait.forHttp("/fhir/metadata")
.forStatusCode(200)
.withStartupTimeout(Duration.ofMinutes(2)));
Reuse Containers¶
@Testcontainers
class AllTests {
@Container
static GenericContainer<?> fhirServer = new GenericContainer<>(...)
.withReuse(true);
}
Security¶
Avoid Hardcoded Credentials¶
// Good
@DynamicFhirServerUrl
static String getServerUrl() {
return System.getenv("FHIR_SERVER_URL");
}
// Avoid
@FhirServerUrl("http://admin:password@server/fhir")
Sanitize Test Data¶
# Use fake data
Fixture: patient from "test-patient.json"
# Contains: John Doe, [email protected], 555-0100
Don't Test Production¶
Common Patterns¶
CRUD Pattern¶
Setup:
* operation create Patient from patient as createResponse
* variable patientId = Patient.id from createResponse
Test: "Read"
* operation read Patient/${patientId}
* assert responseCode = 200
Test: "Update"
* operation update Patient/${patientId} from updatedPatient
* assert responseCode = 200
Test: "Delete"
* operation delete Patient/${patientId}
* assert responseCode = 204
Search Pattern¶
Setup:
* operation create Patient from searchablePatient
Test: "Search by name"
* operation search Patient?name=Smith
* assert responseCode = 200
* assert expression = "Bundle.entry.count() > 0"
Validation Pattern¶
Test: "Validate valid resource"
* operation validate Patient from validPatient
* assert responseCode = 200
* assert expression = "OperationOutcome.issue.where(severity='error').count() = 0"
Test: "Validate invalid resource"
* operation validate Patient from invalidPatient
* assert responseCode = 400
* assert expression = "OperationOutcome.issue.where(severity='error').count() > 0"
Summary¶
Key Takeaways
- Keep tests focused and well-named
- Use fixtures and variables effectively
- Clean up resources in teardown
- Integrate with CI/CD pipelines
- Follow security best practices
Next Steps¶
- Troubleshooting - Common issues and solutions