Sunday 15 September 2019

FHIR Path Validation with SpecFlow

I worked on a project related to FHIR (Fast Health Interoperability Resources), and I was temporarily moved to the test automation team to help them with their workload. I learned soon that they wrote a lot of validators code to interrogate and validate different part of FHIR resources. For example there were ObservationValidator, ImmunizationValidator and the list went on.

I was thinking this was not effective as it did not conform DRY (Don’t Repeat Yourself) principles. I did my research and considering different alternative ways to replace the framework such as using JSON path, XPath and lastly FHIR Path. FHIR Path seems to be no brainer solution to choose from as it is written especially for navigating FHIR resources.

The full specification of the FHIR Path can be found here . Here is some snippet from the web page explaining what FHIR Path is.

FHIRPath is a path-based navigation and extraction language, somewhat like XPath. Operations are expressed in terms of the logical content of hierarchical data models, and support traversal, selection and filtering of data. FHIRPath is used in several places in the FHIR and related specifications:
  1. invariants in ElementDefinition - used to apply co-occurrence and other rules to the contents (e.g. value.empty() or code!=component.code)
  2. slicing discriminator - used to indicate what element(s) define uniqueness (e.g. Observation.category)
  3. search parameter paths - used to define what contents the parameter refers to (e.g. Observation.dataAbsentReason)
  4. error message locations in OperationOutcome
  5. FHIRPath-based Patch
  6. Invariants in the TestScript resource
  7. pre-fetch templates in Smart on FHIR’s cds-hooks

FHIR Path was used extensively in FHIR specification themselves for various purposes and It exposes a lot of C# LINQ semantics. In my case, I used it for validating FHIR resources, and need to build them inside our automation framework, using SpecFlow and xUnit.

The underlying FHIR path code was actually very simple, but not many internet resources available at the moment about its implementation in C#. I used FHIR library from Ewout Cramer (Firely), and I need to dig dipper in their github source code to find out a way to do it.

The code had changed from the first release to the last release. For example, the code to validate that Patient’s Id is not null using FHIR Path based on the latest release, which is Hl7.Fhir.STU3 1.3.0 at the time or writing, is below:

  new Hl7.Fhir.STU3.Model.Patient()
  .ToTypedElement()
  .Predicate(“id.exists()”);

The previous version, which was obsolete, uses IElementNavigator instead of ITypeElement.

  new Hl7.Fhir.STU3.Model.Patient()
  .ToElementNavigator ()
  .Predicate(“id.exists()”);

The earliest version used PocoNavigator which implements IElementNavigator, as shown below:

  new PocoNavigator(new Hl7.Fhir.STU3.Model.Patient())
  .Predicate(“id.exists()”);

The elements, ITypeElement, IElementNavigator and PocoNavigator) are all located in the namespace Hl7.Fhir.ElementModel while the function ‘Predicate’ is an extension method located in the namespace Hl7.FhirPath. So for the code above to compile, both namespaces are required.

The FHIR Path function should be called within SpecFlow tests. In SpecFlow, we write feature files and step definitions and the FHIR Path should be called from SpecFlow step definitions. Following the simple syntax Given, When, Then, here is a simple example of the SpecFlow feature.


Feature: EvaludatioScenarios
 All FhirPath scenarios

@mytag
Scenario: FhirPath Evaluation
 Given I create patient with Id '1' 
 When I evaluate the rule 'id='1''
 Then The result is true
To create a step definition, put a cursor on any of Given, When, or Then statement, and press F12. The prompt will be shown, and click yes to copy the steps to the clipboard. Then create a cs file, put Binding attribute to the class to indicate a step definition class, and paste the method from the clipboard. Below is the complete step definitions for feature above, for all the statements. Note, the scenarioContext is used to communicate the data between statements.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Hl7.Fhir.ElementModel;
using Hl7.Fhir.Model;
using Hl7.FhirPath;
using TechTalk.SpecFlow;
using Xunit;

namespace FhirPathSpecFlowTest
{
 [Binding]
 public sealed class EvaluationSteps
 {
  private readonly ScenarioContext context;
  public EvaluationSteps(ScenarioContext injectedContext)
  {
   context = injectedContext;
  }

  [Given(@"I create patient with Id '(.*)'")]
  public void GivenICreatePatientWithId(string patientId)
  {
   var patient = new Patient();
   patient.Id = patientId;
   context.Set<Patient>(patient);
  }

  [When(@"I evaluate the rule '(.*)'")]
  public void WhenIEvaluateTheRule(string rule)
  {
   Patient patient = context.Get<Patient>();
   var typeElement = patient.ToTypedElement();
   bool result = typeElement.Predicate(rule);
   context.Set<bool>(result);  
  }

  [Then(@"The result is true")]
  public void ThenTheResultIsTrue()
  {
   bool result = context.Get<bool>();
   Assert.True(result);
  }
 }
}


The example project can be downloaded from github. The project was created in Visual Studio Enterprise 2017 version 15.9.12. Below are the complete steps to create the project:
  1. Install SpecFlow for Visual Studio 2017. This Visual Studio extension is required to work with SpecFlow files. The extension adds SpecFlow artifact in the project item selection list, and SpecFlow option in Visual Studio Tools Options.
  2. Create a library project with the .NET Framework with a minimal version 4.5.2. The minimal version is due to the requirement of the latest xUnit version used in the project.
  3. Import nuget package Hl7.Fhir.STU3 1.3.0. This package is required for FhirPath and also FHIR resources.
  4. Import nuget package xunit 2.4.1. This package is required for test assertion in the Specflow step definition and also in the auto-generated features files.
  5. Import nuget packages xunit.runner.visualstudio 2.4.1. This package is required to run xUnit tests in Visual Studio and displays them in the Test Explorer window.
  6. Import nuget package SpecFlow 3.0.225.
  7. Import nuget package SpecFlow.XUnit 3.0.255. This package is used to auto-generate SpecFlow feature files in xUnit syntax.
  8. Import nuget package SpecFlow.Tools.MsBuild.Generation 3.0.225. Before SpecFlow 1.9, the feature code-behind cs files were auto-generated and added to the project in Visual Studio by setting the custom tool property of the feature file to 'SpecFlowSingleFileGenerator'. Since then, SpecFlow allows these files generated by MSBuild at compile time instead, so these files are not required to be included in the project anymore. For this package to work properly, the custom tool property of the feature files must be set to empty, and Visual Studio menu Tool->Options->SpecFlow->Legacy->EnableSpecFlowSingleFileGenerator must be set to false.
  9. Add feature file Evaluation.feature and the step definition file EvaluationSteps.cs mentioned earlier.
  10. Build the project
  11. Open Test Explorer windows. To open this, on the menu click on the Test->Windows->TestExplorer
  12. You should see a test in the windows and click to run it, and the result should be green (success!)

The actual FHIR Path SpecFlow framework I wrote for the project is bigger than what is shown here. Nonetheless, this article captures the essence of what I am doing. The team manager and whole teams were happy with this replacement framework and started playing with the FHIR syntax.

No comments:

Post a Comment