Skip to main content

Jest

Vortex uses Jest as a framework for JavaScript unit testing. Jest tests verify that JavaScript behaviors in custom Drupal modules work correctly in isolation, without requiring a browser or Drupal bootstrap.

Vortex comes with a pre-configured Jest setup.

Usage

ahoy test-js # Run all Jest tests.

Running tests matching a pattern

ahoy test-js does not forward extra arguments, so run subsets through ahoy cli:

ahoy cli "npm test -- ys_demo"

Running a specific test by name

ahoy cli "npm test -- -t 'should increment the value'"

Writing tests

Drupal JavaScript uses the IIFE pattern ((Drupal) => { ... })(Drupal) where Drupal is a global object. Tests load these source files using require() after setting up the required globals.

Test file structure

Test files are placed in a tests/ subdirectory within the js/ directory of each custom module:

web/modules/custom/my_module/
└── js/
├── my_module.js # Source file (Drupal behavior)
└── tests/
└── my_module.test.js # Jest test file

Test template

/**
* @jest-environment jsdom
*/

describe('Drupal.behaviors.myModule', () => {
beforeEach(() => {
localStorage.clear();
global.Drupal = { behaviors: {} };

jest.resetModules();
// eslint-disable-next-line global-require
require('../my_module.js');
});

afterEach(() => {
delete global.Drupal;
});

it('should attach behavior to the context', () => {
document.body.innerHTML = '<div data-my-module></div>';
Drupal.behaviors.myModule.attach(document);

const el = document.querySelector('[data-my-module]');
expect(el.classList.contains('processed')).toBe(true);
});
});

Loading Drupal behaviors

The require() call executes the source file's IIFE, which receives global.Drupal as its Drupal parameter and registers the behavior. After require(), the behavior is accessible via Drupal.behaviors.myModule.

The jest.resetModules() call before require() clears the module cache so the IIFE re-executes on each test with a fresh global.Drupal object.

Mocking globals

Set globals in beforeEach and clean them up in afterEach:

GlobalSetupWhen needed
Drupalglobal.Drupal = { behaviors: {} }Always - required by all Drupal behaviors
jQueryglobal.jQuery = require('jquery') or a mockWhen the source file uses jQuery or $
drupalSettingsglobal.drupalSettings = { path: { baseUrl: '/' } }When the source file reads drupalSettings
localStorageProvided by jsdom; call localStorage.clear()When the source file uses localStorage

Testing DOM interactions

The jsdom environment provides document and window. Set up HTML before each test:

document.body.innerHTML = `
<div data-my-widget>
<button data-action="save">Save</button>
<span data-status></span>
</div>
`;

Drupal.behaviors.myModule.attach(document);
document.querySelector('[data-action="save"]').click();

expect(document.querySelector('[data-status]').textContent).toBe('Saved');

Testing timed behavior

Use Jest fake timers for setTimeout and setInterval:

jest.useFakeTimers();

Drupal.behaviors.myModule.startPolling();
jest.advanceTimersByTime(5000);

expect(fetchSpy).toHaveBeenCalledTimes(5);

jest.useRealTimers();

ESLint compatibility

The .eslintrc.json includes an override for *.test.js files that enables the jest environment and allows global-require. No additional ESLint configuration is needed for test files.

Boilerplate

Vortex provides a Jest test boilerplate for the demo module that demonstrates testing a counter block with DOM manipulation, localStorage interaction, and event handling.

This boilerplate test runs in the continuous integration pipeline when you install Vortex and can be used as a starting point for writing your own JavaScript tests.

Configuration

See Jest configuration reference.

All global configuration takes place in the jest.config.js file.

By default, Jest will discover and run test files with the .test.js extension in web/modules/custom/*/js/ directories and their subdirectories.

The configuration uses the jsdom test environment to provide browser globals like document, window, and localStorage.

Test discovery

The jest.config.js file dynamically scans web/modules/custom/ for modules that contain a js/ directory, and adds each as a root for test discovery. This means adding a new custom module with JavaScript tests requires no configuration changes.

Reports

Coverage

Jest collects code coverage automatically when running tests via ahoy test-js. Coverage is configured in jest.config.js with the following settings:

  • Source files: all *.js files in web/modules/custom/*/js/ directories, excluding *.test.js files
  • Reporters: text (terminal summary), lcov, HTML, and Cobertura XML
  • Output directory: .logs/coverage/jest

After running tests, coverage reports are available at:

ReportLocation
Terminal summaryPrinted to stdout during test run
HTML report.logs/coverage/jest/lcov-report/index.html
LCOV data.logs/coverage/jest/lcov.info
Cobertura XML.logs/coverage/jest/cobertura.xml

Continuous integration

In the continuous integration pipeline, Jest runs on the first container only, see Test parallelism.

The Cobertura XML report is used by continuous integration to track coverage trends and can be consumed by CI tools that support the Cobertura format.

Ignoring failures

Set the VORTEX_CI_JEST_IGNORE_FAILURE environment variable to 1 to ignore failures. The tool still runs and reports violations. See Ignore tool failures.