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.

For running tests, configuration, and CI settings, see the Jest tool reference.

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

Jest automatically discovers *.test.js files in web/modules/custom/*/js/ directories and their subdirectories. Adding a new module with tests requires no configuration changes.

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 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.

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

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.

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 continuous integration pipeline when you install Vortex and can be used as a starting point for writing your own JavaScript tests.