JavaScript unit testing with Jasmine

Commerce uses a custom Grunt task named spec to run Jasmine tests. The task collects the tests from <magento_root_dir>dev/tests/js/jasmine/tests and can be run for all tests, a theme, or a single test.

Prepare environment

Step 1. Install Node.js.

Step 2. Install grunt-cli.

Step 3. In <magento_root_dir>, create Gruntfile.js and copy Gruntfile.js.sample into it.

Step 4. In <magento_root_dir>, create package.json and copy package.json.sample into it.

Step 5. In <magento_root_dir>, install all dependencies:

npm install

Step 6. In <magento_root_dir>, generate static view files that are going to be tested

bin/magento setup:static-content:deploy -f

Note that normally you don't have permissions to <magento_root_dir>/app/code/, in fact the generated static view file is being tested.

For CentOS and Ubuntu users:

If the command fails with the error message:

/var/www/html/magento2ce/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs: error while loading shared libraries: libfontconfig.so.1: cannot open shared object file: No such file or directory

Install fontconfig library:

Learn more in Deploy static view files.

Run tests

Gruntfile.js contains the test run task, so you can run all tests using the following command in the application root directory:

grunt spec:<THEME>

Example:

grunt spec:backend

or for the frontend area:

grunt spec:luma

You can also run a single test:

grunt spec:backend --file="/path/to/the/test.js"

or for the frontend area:

grunt spec:luma --file="/path/to/the/test.js"

Write a test

All tests are distributed through modules stored in <magento_root_dir>/dev/tests/js/jasmine/tests. Let's see how to write a test using an example of an existing test:

app/code/Magento/Ui/base/js/grid/columns/actions.test.js

which tests a JS module:

[<magento_root_dir>/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js]

in its static representations generated in Step 6 previously:

<magento_root_dir>/pub/static/<area>/<theme>/<localisation>/Magento_Ui/js/columns/actions.js.

Step 1. Create a new file with name <fileName>.test.js in an appropriate module directory

For convenience, we can reflect the directory structure of a file to test.

A path to JS module that we want to cover with tests: app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js

A path to a test of the module: app/code/Magento/Ui/base/js/grid/columns/actions.test.js

In <magento_root_dir>/dev/tests/js/jasmine/tests create the test with appropriate path.

Step 2. Require a file that you want to test

For our example we need to cover all static view files ending with Magento_Ui/js/grid/columns/actions.

define([
    'Magento_Ui/js/grid/columns/actions'
], function (Actions) {
    'use strict';

    //Test code
    //...
});

Step 3. Write your Jasmine test code

A Jasmine test consists of main two parts:

Both the describe and it functions contains two parameters:

In describe you can use beforeEach and afterEach functions performing a preparation of what must be done before and after every it test followed.

define([
    'underscore',
    'Magento_Ui/js/grid/columns/actions'
], function (_, Actions) {
    'use strict';

    describe('ui/js/grid/columns/actions', function () {
        var model,
            action;

        beforeEach(function () {
            model = new Actions({
                index: 'actions',
                name: 'listing_action',
                indexField: 'id',
                dataScope: '',
                rows: [{
                    identifier: 'row'
                }]
            });
            action = {
                index: 'delete',
                hidden: true,
                rowIndex: 0,
                callback: function() {
                    return true;
                }
            };
        });

        it('Check addAction function', function () {
            expect(model.addAction('delete', action)).toBe(model);
        });

        it('Check getAction function', function () {
            var someAction = _.clone(action);

            someAction.index = 'edit';
            model.addAction('edit', someAction);
            expect(model.getAction(0, 'edit')).toEqual(someAction);
        });

        it('Check getVisibleActions function', function () {
            var someAction = _.clone(action);

            someAction.hidden = false;
            someAction.index= 'view';
            model.addAction('delete', action);
            model.addAction('view', someAction);
            expect(model.getVisibleActions('0')).toEqual([someAction]);
        });

        it('Check updateActions function', function () {
            expect(model.updateActions()).toEqual(model);
        });

        it('Check applyAction function', function () {
            model.addAction('delete', action);
            expect(model.applyAction('delete', 0)).toEqual(model);
        });

        it('Check isSingle and isMultiple function', function () {
            var someAction = _.clone(action);

            action.hidden = false;
            model.addAction('delete', action);
            expect(model.isSingle(0)).toBeTruthy();
            someAction.hidden = false;
            someAction.index = 'edit';
            model.addAction('edit', someAction);
            expect(model.isSingle(0)).toBeFalsy();
            expect(model.isMultiple(0)).toBeTruthy();
        });

        it('Check isActionVisible function', function () {
            expect(model.isActionVisible(action)).toBeFalsy();
            action.hidden = false;
            expect(model.isActionVisible(action)).toBeTruthy();
        });
    });
});

This topic doesn't provide Jasmine test writing methodology.

Learn more about testing with Jasmine.

Debug tests

Jasmine tests can be debugged in a browser using the following steps:

To keep the webserver running, set keepalive setting to true in the dev/tests/js/jasmine/spec_runner/settings.json file.

Launch the tests with the grunt spec:luma CLI command. Now the webserver should be started and waiting, _SpecRunner.html file should be generated in the project root.

Go to http://localhost:8000/_SpecRunner.html and use the developer console to debug the tests.

The array of the tests can be edited in the _SpecRunner.html file to include only necessary files.

Known issues and solutions

Error: Cannot find module '<module>'

Issue

An error message appears:

Loading "Gruntfile.js" tasks...ERROR

>> Error: Cannot find module '<module>'

Warning: Task "spec" not found. Use --force to continue.

Solution

  1. Make sure your Node.js version is up-to-date.
  2. Remove package.json, Gruntfile.js.
  3. Copy package.json, Gruntfile.js from package.json.sample, Gruntfile.js.sample.
  4. Delete the node_modules directory.
  5. Run npm install in your terminal.

Warning: Cannot read property 'pid' of undefined

Issue

An error message appears:

Warning: Cannot read property 'pid' of undefined

Use --force to continue. Aborted due to warnings.

Solution

Run in your terminal:

cd <magento_root>/node_modules/grunt-contrib-jasmine
npm install

<!-- External --> [<magento_root_dir>/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js]: https://github.com/magento/magento2/blob/2.4/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js