Skip to content

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 correct implementationguides and dependencyExcludes entries
  • <outputDir>/docker-compose.yml — Docker Compose file with bootstrap and test profiles
<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