JavaScript unit testing with Jasmine

Magento 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 a theme .

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 in Magento that are going to be tested

php 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:

  • CentOS:
    yum install fontconfig
    
  • Ubuntu:
    apt-get install fontconfig
    

Learn more in Deploy static view files.

Run tests

Gruntfile.js contains the test run task, so you can run tests for a theme using the following command in the Magento root directory:

grunt spec:<THEME>

Example:

grunt spec:backend

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:

  • describe blocks
  • it blocks

Both the describe and it functions contains two parameters:

  • a text string with description of what is going to be done
  • a function with block of code implementing described action

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

See the full code of the test
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.

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