Unit Testing HL7 Messages
Unit tests verify that your HL7 message creation and parsing logic works correctly in isolation. Good HL7 tests should cover message creation, parsing, field extraction, and round-trip encoding. Use JUnit’s lifecycle methods to set up and tear down HAPI contexts. Test both positive cases (valid messages parse correctly) and negative cases (invalid messages throw appropriate exceptions). Round-trip tests are especially valuable for catching encoding issues.
import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.v25.message.ADT_A01;
import ca.uhn.hl7v2.parser.Parser;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.*;
public class HL7MessageTest {
private HapiContext context;
private Parser parser;
@BeforeEach
public void setUp() {
context = new DefaultHapiContext();
parser = context.getPipeParser();
}
@AfterEach
public void tearDown() throws Exception {
context.close();
}
@Test
public void testCreateADTMessage() throws Exception {
ADT_A01 adtMessage = new ADT_A01();
adtMessage.initQuickstart("ADT", "A01", "P");
adtMessage.getMSH().getSendingApplication()
.getNamespaceID().setValue("TEST_APP");
adtMessage.getPID().getPatientIdentifierList(0)
.getIDNumber().setValue("12345");
String encoded = parser.encode(adtMessage);
assertNotNull(encoded);
assertTrue(encoded.contains("ADT^A01"));
assertTrue(encoded.contains("TEST_APP"));
assertTrue(encoded.contains("12345"));
}
@Test
public void testParseADTMessage() throws Exception {
String hl7Message =
"MSH|^~\\&|SENDING_APP|SENDING_FACILITY|RECEIVING_APP|RECEIVING_FACILITY|20231117120000||ADT^A01|MSG00001|P|2.5\r" +
"PID|1||123456^^^HOSPITAL^MR||DOE^JOHN^A||19800115|M\r";
ADT_A01 message = (ADT_A01) parser.parse(hl7Message);
assertNotNull(message);
assertEquals("SENDING_APP",
message.getMSH().getSendingApplication().getNamespaceID().getValue());
assertEquals("123456",
message.getPID().getPatientIdentifierList(0).getIDNumber().getValue());
assertEquals("DOE",
message.getPID().getPatientName(0).getFamilyName().getSurname().getValue());
assertEquals("JOHN",
message.getPID().getPatientName(0).getGivenName().getValue());
assertEquals("M",
message.getPID().getAdministrativeSex().getValue());
}
@Test
public void testMessageValidation() throws Exception {
String invalidMessage =
"MSH|^~\\&|SENDING_APP||RECEIVING_APP|RECEIVING_FACILITY|20231117120000||ADT^A01|MSG00001|P|2.5\r" +
"PID|1||^^^HOSPITAL^MR||DOE^JOHN^A||19800115|M\r"; // Missing patient ID
assertThrows(Exception.class, () -> {
parser.parse(invalidMessage);
});
}
@Test
public void testMessageRoundTrip() throws Exception {
// Create message
ADT_A01 original = new ADT_A01();
original.initQuickstart("ADT", "A01", "P");
original.getPID().getPatientIdentifierList(0)
.getIDNumber().setValue("999888");
original.getPID().getPatientName(0).getFamilyName()
.getSurname().setValue("TEST");
// Encode
String encoded = parser.encode(original);
// Parse
ADT_A01 parsed = (ADT_A01) parser.parse(encoded);
// Verify
assertEquals(
original.getPID().getPatientIdentifierList(0).getIDNumber().getValue(),
parsed.getPID().getPatientIdentifierList(0).getIDNumber().getValue()
);
assertEquals(
original.getPID().getPatientName(0).getFamilyName().getSurname().getValue(),
parsed.getPID().getPatientName(0).getFamilyName().getSurname().getValue()
);
}
}
Logging and Debugging
Proper logging is essential for troubleshooting HL7 integrations in production. Use SLF4J with Logback for flexible logging configuration. Log messages at appropriate levels: INFO for successful processing summaries, DEBUG for detailed field values, and ERROR for exceptions with full stack traces. The printStructure() method on message objects outputs a formatted view of the message hierarchy, which is invaluable when debugging complex nested structures.
import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.Message;
import ca.uhn.hl7v2.parser.Parser;
import ca.uhn.hl7v2.validation.impl.ValidationContextFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HL7DebugExample {
private static final Logger logger = LoggerFactory.getLogger(HL7DebugExample.class);
public static void main(String[] args) {
String hl7Message =
"MSH|^~\\&|SENDING_APP|SENDING_FACILITY|RECEIVING_APP|RECEIVING_FACILITY|20231117120000||ADT^A01|MSG00001|P|2.5\r" +
"PID|1||123456^^^HOSPITAL^MR||DOE^JOHN^A||19800115|M\r";
try {
logger.info("Starting HL7 message processing");
HapiContext context = new DefaultHapiContext();
context.setValidationContext(ValidationContextFactory.defaultValidation());
Parser parser = context.getPipeParser();
logger.debug("Parsing HL7 message");
Message message = parser.parse(hl7Message);
logger.info("Message parsed successfully");
logger.debug("Message type: {}", message.getName());
logger.debug("Message version: {}", message.getVersion());
// Pretty print message structure
logger.debug("Message structure:\n{}", message.printStructure());
// Log individual segments
ca.uhn.hl7v2.util.Terser terser = new ca.uhn.hl7v2.util.Terser(message);
logger.info("MSH Details:");
logger.info(" Sending App: {}", terser.get("/.MSH-3-1"));
logger.info(" Sending Facility: {}", terser.get("/.MSH-4-1"));
logger.info(" Message Control ID: {}", terser.get("/.MSH-10"));
logger.info("PID Details:");
logger.info(" Patient ID: {}", terser.get("/.PID-3-1"));
logger.info(" Patient Name: {}, {}",
terser.get("/.PID-5-1-1"), terser.get("/.PID-5-2"));
logger.info(" DOB: {}", terser.get("/.PID-7-1"));
logger.info(" Gender: {}", terser.get("/.PID-8"));
context.close();
logger.info("Processing completed successfully");
} catch (Exception e) {
logger.error("Error processing HL7 message", e);
logger.error("Message content: {}", hl7Message);
}
}
}
Testing Tools
A test message generator creates realistic HL7 messages with randomized data for integration testing. This is essential for load testing, stress testing, and validating message handling logic without using real patient data. The generator below creates ADT^A01 messages with random patient names, IDs, and demographics. Extend this pattern to generate other message types (ORU, ORM) for comprehensive testing of your integration.
import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.v25.message.ADT_A01;
import ca.uhn.hl7v2.parser.Parser;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Random;
public class HL7MessageGenerator {
private static final String[] LAST_NAMES =
{"SMITH", "JOHNSON", "WILLIAMS", "BROWN", "JONES", "GARCIA", "MILLER"};
private static final String[] FIRST_NAMES =
{"JAMES", "MARY", "JOHN", "PATRICIA", "ROBERT", "JENNIFER", "MICHAEL"};
private static final String[] GENDERS = {"M", "F"};
private Random random = new Random();
public String generateADTMessage() throws Exception {
HapiContext context = new DefaultHapiContext();
ADT_A01 adtMessage = new ADT_A01();
adtMessage.initQuickstart("ADT", "A01", "P");
// MSH
adtMessage.getMSH().getSendingApplication()
.getNamespaceID().setValue("TEST_GEN");
adtMessage.getMSH().getSendingFacility()
.getNamespaceID().setValue("TEST_FACILITY");
String timestamp = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
adtMessage.getMSH().getDateTimeOfMessage()
.getTime().setValue(timestamp);
adtMessage.getMSH().getMessageControlID()
.setValue("MSG" + System.currentTimeMillis());
// PID
String patientId = String.format("%06d", random.nextInt(999999));
adtMessage.getPID().getPatientIdentifierList(0)
.getIDNumber().setValue(patientId);
adtMessage.getPID().getPatientIdentifierList(0)
.getAssigningAuthority().getNamespaceID().setValue("HOSPITAL");
adtMessage.getPID().getPatientIdentifierList(0)
.getIdentifierTypeCode().setValue("MR");
String lastName = LAST_NAMES[random.nextInt(LAST_NAMES.length)];
String firstName = FIRST_NAMES[random.nextInt(FIRST_NAMES.length)];
adtMessage.getPID().getPatientName(0).getFamilyName()
.getSurname().setValue(lastName);
adtMessage.getPID().getPatientName(0).getGivenName()
.setValue(firstName);
String dob = String.format("%04d%02d%02d",
1940 + random.nextInt(60), // Year
1 + random.nextInt(12), // Month
1 + random.nextInt(28)); // Day
adtMessage.getPID().getDateTimeOfBirth().getTime().setValue(dob);
String gender = GENDERS[random.nextInt(GENDERS.length)];
adtMessage.getPID().getAdministrativeSex().setValue(gender);
Parser parser = context.getPipeParser();
String encodedMessage = parser.encode(adtMessage);
context.close();
return encodedMessage;
}
public static void main(String[] args) {
HL7MessageGenerator generator = new HL7MessageGenerator();
try {
System.out.println("Generating test HL7 messages:\n");
for (int i = 0; i < 5; i++) {
String message = generator.generateADTMessage();
System.out.println("Message " + (i + 1) + ":");
System.out.println(message);
System.out.println();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}