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:
| Global | Setup | When needed |
|---|---|---|
Drupal | global.Drupal = { behaviors: {} } | Always — required by all Drupal behaviors |
jQuery | global.jQuery = require('jquery') or a mock | When the source file uses jQuery or $ |
drupalSettings | global.drupalSettings = { path: { baseUrl: '/' } } | When the source file reads drupalSettings |
localStorage | Provided 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
*.jsfiles inweb/modules/custom/*/js/directories, excluding*.test.jsfiles - Reporters: text (terminal summary), lcov, HTML, and Cobertura XML
- Output directory:
.logs/coverage/jest
After running tests, coverage reports are available at:
| Report | Location |
|---|---|
| Terminal summary | Printed 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.