Testing and Debugging

Section 13 of 14
93% complete

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();
        }
    }
}

Quiz: Testing and Debugging

Question 1 of 5

What JUnit lifecycle method should be used to close the HapiContext after each test?