How to write Javascript Unit Tests for AEM Client Libraries with Jest

This article demonstrates how to set up and run Javascript Jest unit test for client libraries. As a part of this setup, code coverage will be produced by Jest. With this implementation, we will utilise the frontend-maven-plugin, where it makes it possible to run npm scripts apart of the maven build.

Most of the time, AEM developers must create new components in AEM with javascript rich experiences. These experiences may be allowing the end-users to toggle the navigation header, submit a search query, or even to control a slide show. Most of the time we would not need to build a full-blown complex Javascript heavy application using the latest ES6 syntax, ES6 javascript modules, Webpack, etc…; ReactJS and AngularJS are not required.

With a not full-blown Javascript application, we are encouraged to write Javascript logic directly into a client library with ES5; with tools and libraries like Jquery, Underscore JS, Mustache JS, etc… so the Adobe Granite HTML Library Manager will be working as expected.

What is Jest?

Jest is the unit testing framework for front-end driven projects. It is developed and used by Facebook themselves.

Some features of Jest are:

  1. Automatically find tests with files suffixed as test.js; example Header.test.js.
  2. Supports dependency injection.
  3. Supports mocks.
  4. Runs your tests with a fake DOM implementation
  5. and, Runs tests in parallel processes

Setup Introduction

This example is based on AEM project built using AEM project using the AEM Project Archetype 20.

For this example, I have created a javascript file of Helper.js that sits directly under the clientlib-site folder. This client library will exist in the AEM Project Archetype 20 build. Javascript tests should sit within the same directory as the Helper.js file under __tests__, as ./__tests__/Helper.test.js.

On a standard AEM application build, I will run mvn clean install. The application will build as follows:
JEST test flow with AEM build
The run down:

  1. developer runs: mvn clean install.
  2. The core bundle will be built, unit tests from the core maven module will be run.
  3. onSuccess, proceed to the next step; onFail stop build.
  4. Javascript Jest unit tests from the ui.apps maven module will be invoked, and code coverage from the Jest test will be produced.
  5. onSuccess, proceed to the next step; onFail stop build.
  6. The ui.apps.zip will live in the target folder.

How to Setup JEST on an AEM Project

This setup have been tested on the latest AEM Project Archetype 20 project build. If you wish to follow this tutorial, please first build the AEM Project Archetype 20 project.

1. Add Helper.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// create file under directory
// ui.apps/src/main/content/jcr_root/apps/sourcedcode/clientlibs/clientlib-site/js/Helper.js


window.JestTest = window.JestTest || {};

JestTest.Helpers = (function () {

    function add(num1, num2) {
        return num1 + num2;
    }

    function divide(num1, num2) {
        return _secretDivide(num1, num2);
    }

    function _secretDivide(num1, num2) {
        return num1 / num2;
    }

    return {
        add: add,
        divide: divide
    }

}());

2. Add Helper.test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// create file under directory
// ui.apps/src/main/content/jcr_root/apps/sourcedcode/clientlibs/clientlib-site/js/__tests__/Helper.test.js


require('../Helpers');
let Helpers = window.JestTest.Helpers;

beforeAll(() => {
    Helpers = window.JestTest.Helpers;
});

afterAll(() => {
    Helpers = {};
});

it('it should calculate additions as expected 1 + 2 = 3', () => {
    expect(Helpers.add(1, 2)).toEqual(3);
});

it('it should calculate additions as expected 0 + 2 = 2', () => {
    expect(Helpers.add(0, 2)).toEqual(2);
});

it('it should calculate divisions as expected 0 / 2 = 0', () => {
    expect(Helpers.divide(0, 2)).toEqual(0);
});

3. Edit the ui.apps/pom.xml to exclude all directories of __tests__ from being built as apart of the AEM content package (ui.apps)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// add below configuration under -> ui.apps/pom.xml


<!-- out of the box AEM Archetype 20 org.apache.jackrabbit setup -->
<plugin>
    <groupId>org.apache.jackrabbit</groupId>
    <artifactId>filevault-package-maven-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
        <group>com.sourcedcode</group>
        <embeddeds>
            <embedded>
                <groupId>com.sourcedcode</groupId>
                <artifactId>com.sourcedcode.core</artifactId>
                <target>/apps/sourcedcode/install</target>
            </embedded>
        </embeddeds>
        <subPackages>
            <subPackage>
                <groupId>com.adobe.cq</groupId>
                <artifactId>core.wcm.components.all</artifactId>
                <filter>true</filter>
            </subPackage>
            <subPackage>
                <groupId>com.adobe.cq</groupId>
                <artifactId>core.wcm.components.examples</artifactId>
                <filter>true</filter>
            </subPackage>
        </subPackages>


        <!-- custom excludes XML properties -->
        <excludes>
            <exclude>**/*test*</exclude>
        </excludes>
    </configuration>
</plugin>

4. Include the plugin com.github.eirslett, under ui.apps/pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// add below configuration under -> Setup ui.apps/pom.xml


<plugin>
    <groupId>com.github.eirslett</groupId>
    <artifactId>frontend-maven-plugin</artifactId>
    <version>${frontend-maven-plugin.version}</version>
    <configuration>
        <skip>${skipTests}</skip>
    </configuration>
    <executions>
        <execution>
            <id>install node and npm</id>
            <phase>validate</phase>
            <goals>
                <goal>install-node-and-npm</goal>
            </goals>
            <configuration>
                <nodeVersion>${node.version}</nodeVersion>
                <npmVersion>${npm.version}</npmVersion>
            </configuration>
        </execution>
        <execution>
            <id>npm ci</id>
            <phase>initialize</phase>
            <goals>
                <goal>npm</goal>
            </goals>
            <configuration>
                <arguments>install</arguments>
            </configuration>
        </execution>
        <execution>
            <id>npm run test</id>
            <phase>generate-resources</phase>
            <goals>
                <goal>npm</goal>
            </goals>
            <configuration>
                <arguments>run test</arguments>
            </configuration>
        </execution>
    </executions>
</plugin>



5. Include shared maven project properties in parent/pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// add below configuration under -> parent/pom.xml


<properties>
    <!-- out of the box AEM Archetype 20 properties -->
    <aem.host>localhost</aem.host>
    <aem.port>4502</aem.port>
    <aem.publish.host>localhost</aem.publish.host>
    <aem.publish.port>4503</aem.publish.port>
    <sling.user>admin</sling.user>
    <sling.password>admin</sling.password>
    <vault.user>admin</vault.user>
    <vault.password>admin</vault.password>
    <core.wcm.components.version>2.5.0</core.wcm.components.version>
    <bnd.version>4.2.0</bnd.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>


    <!-- custom properties -->
    <skipTests>false</skipTests>
    <frontend-maven-plugin.version>1.6</frontend-maven-plugin.version>
    <node.version>v10.15.0</node.version>
    <npm.version>6.5.0</npm.version>
</properties>

6. Setup ui.apps/package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// add below configuration under -> ui.apps/package.json


{
  "name": "com.sourcedcode",
  "version": "1.0.0",
  "description": "This is an example of testing AEM component's clientlib (with JavaScript) using NodeJS and maven build",
  "scripts": {
    "test": "jest --coverage"
  },
  "author": "SourcedCode",
  "license": "ISC",
  "devDependencies": {
    "babel-jest": "^23.6.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-react": "^6.24.1",
    "jest": "^23.6.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-stage-2": "^6.24.1"
  }
}

7. Add and Setup file ui.apps/.babelrc

1
2
3
4
5
6
7
// create a file names .babelrc
// add below configuration under -> ui.apps/.babelrc


{
  "presets": ["babel-preset-es2015", "babel-preset-stage-2"]
}

The Results

On a successfull build, the results will be as shown:

On a success build, the Jest coverage report will be generated. You can find the generated reports under “ui.apps/coverage”. Click here to learn more about the Jest coverage.

Known Issues:
Please disregard marked [ERROR] tags, as this is a known issue #584. When your Javascript unit tests are invalid, the build will not be successful, and the build will not proceed.

To ensure that you do not lose confidence in the Maven Front-end Plugin, here is an example of Adobe using the same library in it’s production AEM Core Components project.



Last Tips

  • You can run mvn clean install -DskipTests=true if you want to build your AEM project without running Java or Javascript tests; this setup enabled to do so.
  • You can also change directory (CD) into the ui.apps module, and run npm tests, to only run the Jest Tests.
Certified AEM Developer who has been working on AEM software developer for the past 5 years.

Leave a Reply

Your email address will not be published. Required fields are marked *

Back To Top