Parsing Shopify Liquid templates in frontend unit testing for SSR applications

With fully Javascript-generated applications, testing frontend code has become easier and more reliable because all the rendering logic resides in the frontend and only required data is pulled via APIs. But in the case of server-side-rendered (SSR) applications, testing frontend code is challenging.

In SSR code, rendering logics will be on the server. When a browser requests a page, server-side programming languages (such as Ruby, PHP, Java, or Python) will hydrate the required data in the frontend template and return a fully rendered page as a response. So providing a rendering logic for the testing environment is the biggest challenge. Two common ways to solve this problem are:

  • Providing mock HTML data: In this method, the dev will create a mock HTML structure similar to server-rendered data. The main drawback in this method is maintaining parity—whenever a rendering logic changes in the backend, we need to update the mock data accordingly, otherwise test cases won’t give reliable results
  1. Running a separate server: In this method, we need to run our entire application on a separate server and perform testing on it. This will overcome the above drawback but will increase infra costs and testing time based on the size of applications

How we solved this problem

For the Freshworks customer portal, we use Liquid written on Ruby for SSR. Since both of the above methods have their own challenges, we decided to introduce a hybrid approach in which real Liquid templates will be provided as mock data for the frontend test suite. But how will the frontend test suite understand Liquid templates?

LiquidJS

LiquidJS is a Javascript version of Shopify Liquid. It provides a standard Liquid implementation for the Javascript community. We use LiquidJS to parse and render our Shopify Liquid templates as mock data for our frontend test suite. 

Implementation

In our implementation, we’ll use Jest alongside JSDOM and LiquidJS. Jest is a popular Javascript testing framework by Facebook known for its simplicity, fast execution, and easy setup with minimal configuration. JSDOM provides a virtual browser environment, essential for testing frontend code in a non-browser environment like Node.js, as it simulates browser behavior and interactions without the need for an actual browser.

First, install the necessary npm packages with npm install jest and npm install liquidjs. Then, let’s create a product.liquid file, which simply iterates over a given object and constructs a list.

Liquid JS

Now let’s write a test to parse the above Liquid file with Freshdesk and Freshservice as products param, render in JSDOM, and test whether the first list text is Freshdesk. By default, Jest will not run the JSDOM environment, so we are explicitly instructing Jest to use the JSDOM environment.

Liquid JS 2

Then we need to read the Liquid file and append the content of the rendered Liquid file into JSDOM. Here, the renderFileSync method takes two parameters. The first parameter is file name (we don’t need to give the file extension and full path, since it’s defined while initializing), and the second parameter is input for the Liquid file.

Liquid JS 3

Finally, we need to check if the content of the first li is Freshdesk.

Liquid js 4

Our final code will look like this:

liquid js 5

The result of the test will be: 

Handling customized Liquid tags/filters

LiquidJS has all the common tags and filters implemented in Shopify Liquid; however, we have a lot of custom tags and filters. So we need to register them for LiquidJS to understand. A simple example for implementing a custom tag called upper:

Liquid JS 7

This hybrid approach, leveraging real Liquid templates as mock data without the need for a backend server, offers a nuanced testing solution. While it demands some initial effort in setup and writing custom tags/filters, the payoff is substantial. It markedly increases test accuracy, ensuring frontend stability even when backend templates change. This method effectively bridges the gap between frontend testing reliability and backend dynamics, making it a more effective testing solution for frontend apps that utilize Shopify Liquid templates in the backend.