Ant Task¶
Execute FHIR tests using Apache Ant for build integration.
Perfect for FHIR IG Publisher Integration
The Ant task is designed for integration with the FHIR IG Publisher build process.
A single ant command initialises the server, bootstraps a pre-warmed Docker image,
and executes TestScripts — all reproducibly and without manual setup.
Overview¶
FHIR Frog provides three Ant tasks that cover the complete testing lifecycle:
| Task | Class | Purpose |
|---|---|---|
<fhirinit> |
FhirInitTask |
Generate HAPI FHIR config from sushi-config.yaml |
<fhirbootstrap> |
FhirBootstrapTask |
One-time warm-up and Docker commit of HAPI server |
<fhirtest> |
FhirTestTask |
Execute TestScripts or TestPlans; write Surefire XML |
Setup¶
Download the fat JAR and register the tasks in your build.xml:
<property name="fhir.frog.jar" value="${user.home}/.fhir-frog/lib/fhir-frog-ant-1.0.0-all.jar"/>
<!-- Auto-download the JAR from GitHub Releases -->
<target name="fetch-jar">
<available file="${fhir.frog.jar}" property="jar.already.present"/>
<antcall target="-do-fetch-jar"/>
</target>
<target name="-do-fetch-jar" unless="jar.already.present">
<mkdir dir="${user.home}/.fhir-frog/lib"/>
<get src="https://github.com/aehrc/fhir-frog/releases/download/v1.0.0/fhir-frog-ant-1.0.0-all.jar"
dest="${fhir.frog.jar}" verbose="true" usetimestamp="true"/>
</target>
<!-- Register tasks after JAR is present -->
<target name="-load-tasks" depends="fetch-jar">
<taskdef resource="org/fhirfrog/frog/ant/antlib.xml" classpath="${fhir.frog.jar}"/>
</target>
The Bootstrap Problem¶
IG packages can be large. A cold HAPI FHIR startup that downloads and indexes AU eRequesting (and its transitive dependencies) typically takes 10–20 minutes.
FHIR Frog solves this with a one-time docker commit that snapshots the pre-warmed
server state as a reusable Docker image. Subsequent test runs restore the snapshot
in ~25 seconds.
Cold start (no snapshot) Snapshot workflow
───────────────────────── ──────────────────────────────────
Start HAPI container First run only:
↓ Start HAPI → load IGs → docker commit → image
Download IG packages (~10 min)
↓ Every subsequent run:
Load & index packages Restore snapshot → run tests (~25 sec)
↓
Run tests
Task Reference¶
<fhirinit> — Generate HAPI Configuration¶
Reads sushi-config.yaml, resolves transitive IG dependencies from the local FHIR
package cache (~/.fhir/packages), and generates:
<outputDir>/application.yaml— HAPI configuration with correctimplementationguidesanddependencyExcludesentries<outputDir>/docker-compose.yml— Docker Compose file withbootstrapandtestprofiles
<fhirinit igConfig="sushi-config.yaml"
fhirVersion="R4"
outputDir=".fhir-frog"
imageTag="hapi-myig:1.0.0"/>
| Attribute | Default | Description |
|---|---|---|
igConfig |
sushi-config.yaml |
Path to IG config file |
fhirVersion |
R4 |
Target FHIR version (R4 or R5) |
outputDir |
.fhir-frog |
Directory to write generated files |
imageTag |
(required) | Docker image tag for the committed snapshot |
<fhirbootstrap> — Bootstrap HAPI Image¶
Checks whether imageTag already exists locally. If not, runs the bootstrap
Docker Compose profile, waits for HAPI to become healthy, commits the container
state as a Docker image, and tears down the bootstrap containers.
<fhirbootstrap imageTag="hapi-myig:1.0.0"
composeFile=".fhir-frog/docker-compose.yml"
containerName="fhir-frog-hapi-bootstrap"/>
| Attribute | Default | Description |
|---|---|---|
imageTag |
(required) | Docker image tag for the committed snapshot |
composeFile |
.fhir-frog/docker-compose.yml |
Path to the Docker Compose file |
containerName |
fhir-frog-hapi-bootstrap |
Name of the HAPI container to commit |
Idempotent
If imageTag already exists locally, <fhirbootstrap> exits immediately.
Running ant multiple times is safe.
<fhirtest> — Execute Tests¶
Executes a TestScript or TestPlan against a running FHIR server and optionally writes Surefire-format XML reports.
<fhirtest testPlan="tests/suite.json"
serverUrl="http://localhost:8080/fhir"
reportDir="target/fhir-reports"
reportFormat="both"
failOnError="true"/>
| Attribute | Default | Description |
|---|---|---|
testScript |
— | Path to a TestScript file (JSON, XML, or TSH) |
testPlan |
— | Path to a TestPlan file |
serverUrl |
(required) | FHIR server base URL |
reportDir |
— | Directory for Surefire XML output |
reportFormat |
json |
json, xml, or both |
failOnError |
true |
Fail the build on test failure |
verbose |
false |
Enable verbose output |
IG Publisher Integration¶
FHIR Frog integrates naturally with the HL7 FHIR IG Publisher. The recommended
pattern is to add a build.xml alongside ig.ini that runs tests as part of
each publication build.
<?xml version="1.0" encoding="UTF-8"?>
<project name="my-ig" default="test" basedir=".">
<!-- ─── Properties ────────────────────────────────────────────────── -->
<property name="fhir.frog.version" value="1.0.0"/>
<property name="fhir.frog.jar"
value="${user.home}/.fhir-frog/lib/fhir-frog-ant-${fhir.frog.version}-all.jar"/>
<property name="fhir.frog.release.url"
value="https://github.com/aehrc/fhir-frog/releases/download/v${fhir.frog.version}/fhir-frog-ant-${fhir.frog.version}-all.jar"/>
<property name="image.tag" value="hapi-myig:1.0.0"/>
<property name="server.url" value="http://localhost:8080/fhir"/>
<property name="report.dir" value="output/test-reports"/>
<!-- ─── Bootstrap ─────────────────────────────────────────────────── -->
<target name="fetch-jar">
<available file="${fhir.frog.jar}" property="jar.already.present"/>
<antcall target="-do-fetch-jar"/>
</target>
<target name="-do-fetch-jar" unless="jar.already.present">
<dirname property="fhir.frog.jar.dir" file="${fhir.frog.jar}"/>
<mkdir dir="${fhir.frog.jar.dir}"/>
<get src="${fhir.frog.release.url}" dest="${fhir.frog.jar}"
verbose="true" usetimestamp="true"/>
</target>
<target name="-load-tasks" depends="fetch-jar">
<taskdef resource="org/fhirfrog/frog/ant/antlib.xml" classpath="${fhir.frog.jar}"/>
</target>
<!-- ─── Init ──────────────────────────────────────────────────────── -->
<target name="init" depends="-load-tasks"
description="Generate HAPI config and bootstrap Docker image">
<fhirinit igConfig="sushi-config.yaml"
fhirVersion="R4"
outputDir=".fhir-frog"
imageTag="${image.tag}"/>
<fhirbootstrap imageTag="${image.tag}"
composeFile=".fhir-frog/docker-compose.yml"/>
</target>
<!-- ─── Test ──────────────────────────────────────────────────────── -->
<target name="test" depends="init"
description="Run FHIR conformance tests">
<mkdir dir="${report.dir}"/>
<fhirtest testPlan="tests/conformance-suite.json"
serverUrl="${server.url}"
reportDir="${report.dir}"
reportFormat="both"
failOnError="true"/>
</target>
</project>
Run with:
# First run: downloads JAR, bootstraps Docker image (~15 min), runs tests
ant fetch-jar && ant init && ant test
# Subsequent runs: restores snapshot, runs tests (~25 sec)
ant test
Surefire XML Output¶
When reportDir is set, <fhirtest> writes one TEST-<name>.xml per execution
in the Surefire format consumed by Jenkins, GitLab CI, and mvn surefire-report:report:
<testsuite name="patient-create" tests="3" failures="1" errors="0" time="0">
<testcase name="create patient" classname="patient-create" time="0"/>
<testcase name="read created patient" classname="patient-create" time="0"/>
<testcase name="delete patient" classname="patient-create" time="0">
<failure message="Expected 204 but was 405">...</failure>
</testcase>
</testsuite>
Next Steps¶
- JUnit — JUnit 5 extension
- Maven — Maven plugin
- CLI — Command-line usage
- AU Core Example — Complete end-to-end example