How to Unit Test a Custom Block in Element

Element has built-in unit testing of Blocks using Jest and Enzyme. Here’s how to unit test a custom Block in order to make it more maintainable and reliable.

What is a Unit Test?

Unit tests are a type of automated software testing used to improve reliabili ty of software.

They help with maintaining and changing the code because they help prevent bugs.

All programmers test their code manually to make sure it works, but automated testing helps ensure the code is working without manually testing every possibility.

Having unit tests help you detect changes that break existing features in your code.

The Element platform makes it easy to include unit tests in your custom Blocks built for the VOLT store .

This article will discuss the rationale for unit testing, the default unit tests that come with a new Block, and how to add unit tests to a custom Block.

Why Unit Test a Custom Block?

All custom Blocks are required to have passing tests in order to be published for use in VOLT’s Site Designer.

While it is possible to skip or delete the tests and still publish a custom Block, it is highly recommended to include unit tests.

Having unit tests will make sure that future updates don’t break the expected functionality.

Sudden breaking changes can cost real money in ecommerce, because a bug can mean that a customer is suddenly unable to use a VOLT store.

Unit testing helps prevent these sudden, unexpected bugs that occur when fixing other bugs or adding new features.

How Do You Run Unit Tests in Element?

When you create a new Block in Element using the element new command in the Element CLI, it comes with basic test files in the /__test__/ files.

For a refresher on making a custom Block using Element, please refer to the article “Building An Element Page Tutorial ” (15 minute read) in the Element Platform Documentation or watch the YouTube video playlist “Element Tutorial | VOLT Learning Center ” (27 minutes playtime).

The built-in tests in the starter Block can be launched using in the npm run test command in a new terminal window:

npm run test

This command runs all the unit tests by starting the built-in Jest test runner in “watch mode ” -- meaning the tests continuously run when any file in the custom Block is changed.

“A test runner is the library or tool that picks up an assembly (or a source code directory) that contains unit tests, and a bunch of settings, and then executes them and writes the test results to the console or log files.” —Omar Tawfik on Quora 

Using watch mode lets you see your tests in real-time, as you are coding.

Any additional files added to the /__test__/ directory with the extension .spec.js (for JavaScript Specification) will be automatically run by the test runner.

The npm run build command also runs all the tests before building the package. This step is necessary before publishing a custom Block to the VOLT store using the element publish command .

What’s the Difference Between Jest and Enzyme?

The starter Block in Element comes with two different testing tools, Jest and Enzyme. These are npm packages (JavaScript libraries) that provide the testing capabilities used inside Element.

Both Jest and Enzyme are installed automatically when you run the npm install command inside a new custom Block.

Jest and Enzyme are similar but provide slightly different functionality.

Jest is a testing framework that provides the test runner used by Element. It includes an assertion library (meaning functions that allow you to write unit tests). Jest works great for testing React, but it could be used for any JavaScript program.

Enzyme is a library specifically for testing React components . It is used in combination with a full testing library, like Jest.

Using Enzyme helps us write more traditional unit tests, testing individual aspects of our functionality.  

Element uses Enzyme to write the tests and Jest to run them. Enzyme is useful for rendering (or displaying) custom Blocks for testing, because Blocks are based on React components.

Jest has the additional functionality to make “snapshots” that are used to make sure the user interface does not change unexpectedly. Snapshot testing is a useful feature that will be discussed in a future tutorial article.

For more information, please see the documentation for Jest (GitHub, docs) and Enzyme (GitHub, docs).

What are the Default Unit Tests in Element?

The default unit tests in Element use Enzyme to test the output of the starter Block. The file /__spec__/Block.spec.js contains the following code by default:

import React from "react"
import { mount } from "enzyme"
import {
mockUtils as utils,
joinClasses,
} from "@volusion/element-block-utils/test-utils"
import { block as Block, defaultConfig } from "../src"
let props
describe("The Block", () => {
beforeEach(() => {
props = {
data: {},
utils,
joinClasses,
queryParams: {},
}
})
it("renders without errors", () => {
mount(<Block {...props} />)
})
describe("when there is no custom data", () => {
it("should render the block with the default content", () => {
const wrapper = mount(<Block {...props} />)
expect(wrapper.text()).toBe(defaultConfig.text)
})
})
describe("when given custom data", () => {
it("should render the block using the custom data", () => {
const customText = "Custom Block Text"
const wrapper = mount(<Block {...props} text={customText} />)
expect(wrapper.text()).toBe(customText)
})
})
})

The /__tests__/Block.spec.js test file includes three unit tests by default:

  1. The Block should render (meaning it displays itself) without errors.
  2. The Block should render the default content (from /src/configs.js) when there is no custom data.
  3. The Block should render custom content (specified in the test) when there is custom data.

The beforeEach code block runs before each unit test and re-initializes the props that will be passed to the custom Block to a default, empty state.

Each unit test includes a call to the Enzyme function mount(), which mounts the React component (the custom Block) for testing.

The Jest test runner will find the function expect(), which is used in combination with .toBe() to assert (make sure) that the values are equal. If they do, the test will pass. If they don’t, the test will fail.

Testing the Google AMP Version of a Custom Block

The Google AMP (Accelerated Mobile Pages) test file (/__tests__/AMP.spec.js) is the same as the Block test file, except one line (Line 14):

utils: { ...utils, isAmpRequest: true },

This line asks Element for the Google AMP version of the page.

Google AMP pages have certain requirements , which are handled automatically for you by Element. For more information on optimizing your custom Block for performance, please read the article Keeping Your Site Fast in the Element Platform Documentation.

How do you write new unit tests in Element?

Because Jest and Enzyme are built-in, Element makes it easy to write and modify unit tests to test the functionality of your custom Block. The rest of this tutorial builds on the previous article showing how to build a Custom Font Size Heading Block.

Please refer back to that article, How to Make Font Size Editable in an Element Block , for the code to create that custom Block.

You will be adding unit tests to test this Block, instead of skipping them as in that tutorial. If you would like to follow along exactly, follow the previous tutorial until the heading “Update your test files to skip the tests.”

The remainder of this article discusses writing test files to test the functionality of that custom Block.

Adding Unit Tests to a Custom Block

In your favorite text editor or IDE (such as VS Code), open the /__tests__/Block.spec.js file.

Replace the entire contents of the file with the following code:

import React from "react"
import { mount } from "enzyme"
import {
mockUtils as utils,
joinClasses,
} from "@volusion/element-block-utils/test-utils"
import { block as Block, defaultConfig } from "../src"

let props
describe("The Block", () => {
beforeEach(() => {
props = {
data: {},
utils,
joinClasses,
queryParams: {},
}
})
it("renders without errors", () => {
mount(<Block {...props} />)
})
describe("when there is no custom data", () => {
it("should render the block with the default content", () => {
const wrapper = mount(<Block {...props} />)
expect(wrapper.text()).toBe(defaultConfig.heading + defaultConfig.text)
})
})
describe("when given custom data", () => {
it("should render the block using the custom data", () => {
const customHeading = "Custom Block Heading"
const customText = "Custom Block Text"
const wrapper = mount(
<Block {...props} heading={customHeading} text={customText} />
)
expect(wrapper.text()).toBe(customHeading + customText)
})
})
})

This test file is similar to the default unit tests. Only tests 2 and 3 have been modified (Lines 25-35).

Now edit the /__tests__/AMP.spec.js file, using the same code, but change Line 14 to read:

utils: { ...utils, isAmpRequest: true },

Try running the tests using npm run test, and you should see all 6 tests pass:

PASS __test__/Block.spec.js (5.979s)
PASS __test__/AMP.spec.js (5.994s)
Test Suites: 2 passed, 2 total
Tests: 6 passed, 6 total

If the test runner was already running in watch mode, then you should see the tests change from failing to passing as you save the files.

The rest of the article explains how the new unit tests for the Custom Font Size Heading Block work compared to the default tests.

Unit Testing the Default Content

The 2nd unit test examines the behavior of the Block when no custom data is provided.

The original line (Line 25) in /__tests__/Block.spec.js from the starter Block reads:

 expect(wrapper.text()).toBe(defaultConfig.text)

This test asserts that the default text content of the starter Block (accessed using the Enzyme .text() function) is the same as the default text (from /src/configs.js).

The new line (Line 25) in /__tests__/Block.spec.js in the Custom Font Size Heading Block reads:

 expect(wrapper.text()).toBe(defaultConfig.heading + defaultConfig.text)

This test asserts that the default text content of the custom Block is the same as the result of combining (or concatenating ) the default heading and default text (from /src/configs.js).

Debugging the Unit Tests

The Jest test runner will display console.log() messages, even in watch mode, allowing easy debugging of unit tests as you write them. Logging messages to the console is especially useful combined with the built-into Enzyme function .debug() , which outputs a String from a mounted React component (the Block).

To try these functions, you could change the 2nd test in /__tests__/Block.spec.js to read:

describe("when there is no custom data", () => {
it("should render the block with the default content", () => {
const wrapper = mount(<Block {...props} />)
console.log(wrapper.debug())
console.log(wrapper.text())
expect(wrapper.text()).toBe(defaultConfig.heading + defaultConfig.text)
})
})

This will produce the following output in the test runner:

console.log backup/Block.spec.js:25
<Block
data={{...}}
utils={{...}}
joinClasses={[Function: joinClasses]}
queryParams={{...}}
heading="Custom heading"
headingFontSizeString="2em"
text="Custom text"
textFontSizeNumber={24}
cssLengthUnit="px"
>
<h1 className="heading_d52pcl">Custom heading</h1>
<p className="text_1ab54ap">Custom text</p>
</Block>

console.log backup/Block.spec.js:26
Custom headingCustom text

Next you will try unit testing the custom content passed as props (React properties) to the Block.

Unit Testing the Custom Content

The 3rd unit test makes sure that the Block correctly displays custom data provided to it in the form of props..

The new lines (Lines 30-35) in /__tests__/Block.spec.js in the Custom Font Size Heading Block read:

const customHeading = "Custom Block Heading"
const customText = "Custom Block Text"
const wrapper = mount(
<Block {...props} heading={customHeading} text={customText} />
)
expect(wrapper.text()).toBe(customHeading + customText)

This test asserts that the custom Block correctly displays the custom heading and custom text provided inside the test. These strings are passed as additional props to the custom Block.

This unit test effectively replaces the manual testing that could be performed by changing the local testing data in /local/index.js (in the const localEnvPropOverrides) and looking at the changes in the local development environment (started using npm run start).

Conclusion

Unit testing is a useful type of automated software testing that makes sure that the code behaves as expected. It helps prevent bugs caused by future changes to the code.

Because unit testing is so important for reliability, having passing unit tests is a requirement of publishing your custom Blocks to VOLT’s Site Designer. While you can get around this requirement by deleting or skipping the tests, learning how to unit test your custom Block will reduce future maintenance by preventing bugs. The unit tests shown in this article would allow you to make updates to the Custom Font Size Heading Block while being sure that the changes did not cause problems.

For example, you could add a font color picker to the custom Block while being sure the text output remains the same. For information on doing so, please refer to the article How To Style A Block With Aphrodite in the Element Platform Documentation.