Unit Tests for Liquid Templates
As we use developer tools and syntax to write Liquid templates, it would make sense to write unit test cases. We can ensure that the templates produce the correct result using a variety of data sources. I have not seen any useful implementations to achieve this level of quality assurance, so I have created an open source library that give Liquid unit test support.
This post is the second in a series about the Liquid language.
Background
The Liquid template engine that is used in Microsoft Azure is based on the DotLiquid library.
DotLiquid is a .Net port of the popular Ruby Liquid templating language. It is a separate project that aims to retain the same template syntax as the original, while using .NET coding conventions where possible. For more information about the original Liquid project, see https://shopify.github.io/liquid/.
DotLiquid project on Github
- control flow
- iteration
- variables
- templates
- filters
- data selectors
{% comment -%}
Short sample showing how iteration and data selectors with in Liquid
{% endcomment -%}
{% assign products = data.payload.warehouse.products -%}
{% for product in products -%}
-
{{product.name}}
Only {{product.price | price }}
{{product.description | prettyprint | paragraph }}
{% endfor -%}
- read the Liquid template language reference documentation
- read the DotLiquid documentation
- try the DotLiquid online test bench
- try the LiquidJS playground
- install the Liquid VSCode plugin for LINT and templates
Azure Specific Differences
A key within this project is that I created it to be as compatible and usable for Azure implementations as possible. Therefore, it is important to understand how the DotLiquid and Azure implementations of the library differ from Shopify Liquid.
- Liquid templates follow the file size limits for maps in Azure Logic Apps.
- When using the date filter, DotLiquid supports both Ruby and .NET date format strings (but not both at the same time). By default, it will use .NET date format strings.
- The JSON filter from the Shopify extension filters is currently not implemented in DotLiquid.
- The standard Replace filter in the DotLiquid implementation uses regular expression (RegEx) matching, while the Shopify implementation uses simple string matching.
- Liquid by default uses Ruby casing for output fields and filters such as {{ some_field | escape }}. The Azure implementation of DotLiquid uses C# naming convention, in which case output fields and filters would be referenced like so {{ SomeField | Escape }}.
For further details, see the Microsoft documentation.
Unit Testing Liquid Templates
First of all, take a look at the source code repository in Github. Azure uses a set of predefined feature uses of DotLiquid. For example, an Azure Logic App mapping service uses the “content” accessor for any data submitted using a workflow action. The LiquidParser class exposes a set of SetContent methods used to either set:- objects (will render down to JSON)
- JSON string
- XML string (will parse as XDocument then to JSON)
{% assign albums = content.CATALOG.CD -%}
[{%- for album in albums limit:3 %}
{
"artist": "{{ album.ARTIST }}",
"title": "{{ album.TITLE}}"
}{% if forloop.last == false %},{% endif %}
{%- endfor -%}
]
- Create a new test project – I use the XUnit framework but it’s up to you
- Add a reference to the AzureLiquid Nuget package
- I also use the FluentAssertions package, you can optionally add this
- Create a resource file
- Add three files:
- Liquid template
- Source JSON or XML data file
- Expected outcome file
- Add the files as file resources to your resource file and set file type to text (properties pane for the resource)
- Use the arrange-act-assert pattern to ensure the files work as example below.
using System.Text.RegularExpressions;
using AzureLiquid;
using FluentAssertions;
using Xunit;
public class JsonLiquidTests
{
///
/// Ensures loading JSON from a file and parsing with a liquid file template works.
///
[Fact]
public void EnsureJsonBodyTemplateParsing()
{
// Arrange
var expected = Resources.JsonTestExpected;
var parser = new LiquidParser()
.SetContentJson(Resources.JsonTestContent)
.Parse(Resources.JsonTestTemplate);
// Act
var result = parser.Render();
// Assert
result.Should().NotBeEmpty("A result should have been returned");
CompareTextsNoWhitespace(result, expected).Should()
.BeTrue("The expected result should be returned");
}
///
/// Compares two text snippets but ignores differences in whitespace.
///
/// The first text.
/// The second text.
/// true if the texts match, otherwise false .
private static bool CompareTextsNoWhitespace(string text1, string text2)
{
var spaces = new Regex(@"[\s]*");
return string.CompareOrdinal(spaces.Replace(text1, string.Empty), spaces.Replace(text2, string.Empty)) == 0;
}
}
There are several more examples within the source code repository. If you have any questions, find any issues or have a feature request, then use the discussions section of the repository.