WordPress has a long record of unit testing its PHP code. However, writing JavaScript unit tests and integration tests for the code in themes and plugins doesn’t enjoy the same status. Let’s see how to test the JavaScript in our WordPress plugins and themes using Jest.
These are the contents that we’ll cover in this article:
JavaScript Unit Tests in WordPress
Benefits of using Jest for JavaScript unit tests in WordPress
Mock styles import for Jest tests
Overview of the JavaScript file
Writing tests with Jest, finally!
JavaScript integration test with Jest in asynchronous code that uses fetch
JavaScript Unit Tests in WordPress
So why write unit tests and integration tests? They’re great ensure that a function, class method, or even a React component, does what they’re supposed to do. It helps detect errors in the code and also helps preserve integrity when changes are later introduced in the code by verifying it’s still working as intended after the update.
WordPress plugins and themes are mostly written in PHP and can use PHPUnit suite for running tests, making assertions, mocking functions and more. The handbook even has a section explaining how to quickly setup unit testing with the WP-CLI.
For JavaScript, there’s a page about running QUnit tests in the Handbook, although it’s more about testing the JavaScript code in WordPress core.
While none of this is standardized for JavaScript in WordPress, we can write great unit tests using the Jest testing suite. In this article we’ll learn how to write JavaScript unit tests for an example plugin that loads 5 posts through the WP REST API using the fetch
function from JavaScript and renders them in a certain DOM element in the page.
Benefits of using Jest for JavaScript unit tests in WordPress
In the past, writing JavaScript unit tests involved setting up Mocha to run tests, Chai to make assertions, and Sinon to mock functions and spy on them. While they offer great flexibility, it’s much more complex to work with three packages than one. Jest provides all this in a single package:
- you can run tests declaring them with
describe
andit
ortest
- you can make assertions with
expect
andtoBe
,toHaveLength
and many more - you can mock functions and spy on them, and there are multiple ways to do this
Before moving on
In order to keep this article focused on testing with Jest, there’s additional setup external to the process of testing like Babel or Webpack that won’t be covered here. All this can be found in the WP Jest Tests repo that accompanies this article. To keep things contextual, each section will link to one of the relevant files that we’ll write.
Overview of the PHP side
The PHP for the example plugin that we’ll use to learn how to write the JavaScript unit tests is fairly standard. The only interesting lines are those when we enqueue our JavaScript file and add an inline script to pass variables to it:
$initial_state = rawurlencode( wp_json_encode( array(
'restUrl' => esc_url_raw( rest_url() ),
'posts' => 5,
) ) );
wp_enqueue_script(
'wp-jest-tests',
plugins_url( 'front.js', __FILE__ ),
array(),
$plugin_version,
true
);
wp_add_inline_script(
'wp-jest-tests',
'window.wpJestTests = JSON.parse(decodeURIComponent("' . $initial_state . '"));',
'before'
);
After enqueing our JavaScript file, we add a global variable wpJestTests
with wp_add_inline_script()
. This variable is particularly interesting and tricky to deal with because it’s external to our JavaScript file and we’ll have to mock it in our tests.
Set up Jest tests
We’ll first have to initialize npm
in our plugin project to be able to install Jest with it. Navigate in the command line to your plugin folder and run:
npm init
and go through the series of prompts here
Edit the package.json file that was created and add a new entry to run our tests in the scripts
section:
“test”: “jest”
This will be the command we’ll use to tell Jest to run our JavaScript unit tests. This is a one off run, but Jest also supports file watching, so you can enter an additional one:
“test-watch”: “jest --watch”
Now install Jest as a development dependency by running
npm install -D jest
While it’s installing, create a file named jest.config.js
. This will hold all the configuration necessary. Add the following:
module.exports = {
verbose: true,
setupFiles: [
'<rootDir>/__mocks__/globals.js'
],
moduleNameMapper: {
'\\.(css|less|sass|scss)$': '<rootDir>/__mocks__/styleMock.js'
}
};
Let’s go through each one:
verbose
: indicates whether each individual test should be reported during the run. All errors will also still be shown on the bottom after execution. Note that if there is only one test file being run it defaults totrue
.setupFiles
: a list of paths to modules that run some code to configure or set up the testing environment. Each setupFile will be run once per test file. Since every test runs in its own environment, these scripts will be executed in the testing environment immediately before executing the test code itself. We’ll use this to declare global variables rendered by PHP and WordPress with commands likewp_add_inline_script
orwp_localize_script
.moduleNameMapper
: this is a map from regular expressions to module names (or arrays of module names) that allow to stub out static resources, like images or styles with a single module.
You surely noticed the <rootDir> reference: this is a special token that gets replaced by Jest with the root of your project, which is usually, the location of your package.json
.
We’ll go in-depth in the next two sections but first, create the folder and these two files referenced above:
- create a folder named
__mocks__
- inside the folder, create the
styleMock.js
andglobals.js
files
Mock styles import for Jest tests
If, like this plugin, you use Webpack to compile everything including styles and you’re importing the .scss
file in the .js
file:
import './main.scss';
you need to use styleMock.js to mock SASS files when importing them in our JavaScript file, otherwise Jest will crash since it won’t be able to resolve the module. You don’t need much for this file, just add
/* nothing */
Jest will use this mock every time it finds a .scss
import and will map them to the mock, allowing you to move forward with tests without caring about the styles.
Set up globals for Jest
Let’s work now in the globals.js file. One of the perks of using Jest, is that it already ships with an already configured jsdom
, a pure-JavaScript implementation of web standards, which simulates a DOM environment as if you were in the browser, and we’ll use it to mock DOM elements for our tests.
Create a folder __mocks__
and a globals.js
file inside it. Add this to the file:
import { JSDOM } from 'jsdom';
const dom = new JSDOM();
global.document = dom.window.document;
global.window = dom.window;
global.window.wpJestTests = undefined;
This will declare and mock some objects and methods that you’ll later use in your tests. The last one is of particular interest
global.window.wpJestTests = undefined;
This is the global variable we wrote using wp_add_inline_script
. You need to mock it as a global variable to be able to use it in your tests.
Overview of the JavaScript file
The WordPress plugin has a single JavaScript file main.js in a /src
folder. This is later transpiled and output in a /js/front.js
file which is what the PHP will load. The JavaScript loads five WordPress posts using fetch
, a standard JavaScript function, through the WP REST API and inserts its title, with a link to the post, into a div.entry-content
.
You’re going to export the function that does all the work:
export const wpJestTestsInit = () => {
so you can use it in your tests in Jest.
Writing JavaScript unit tests with Jest, finally!
Now you can start writing tests! Jest can find tests in many ways:
- if the file name is
test.js
- or is prefixed with the name of the file it tests, like
main.test.js
- if they’re
.js
files located in a folder named__tests__
Create the __tests__
folder and inside it, a front.test.js
. You can see the finished JavaScript file for the Jest tests in the companion repo. Let’s go through it in blocks. The first line imports the JS file that we want to test:
import { wpJestTestsInit } from '../src/main';
Next, we clear all mocks, so we never run with dirty mocks carrying values from previous tests. Doing this can lead to errors since for example, if we’re spying on how many times a mocked function was called, we could get an incorrect reading if we don’t clear the mock between test and test:
afterEach( () => {
jest.clearAllMocks();
} );
The first test we’ll write, makes basic assertions when some things fail. For example, if the global wpJestTests
variable that we were writing with wp_add_inline_script
wasn’t written for any reason:
describe( 'Initialization', () => {
test( 'if wpJestTests is undefined, nothing happens', () => {
expect( wpJestTestsInit() ).toBe( false );
} );
test( 'if .entry-content is missing, nothing happens', () => {
wpJestTests = { posts: 5 };
const mockQuerySelector = jest.spyOn( document, 'querySelector') .mockImplementation( () => undefined );
expect( wpJestTestsInit() ).toBe( false );
expect( mockQuerySelector ).toHaveBeenCalledTimes( 1 );
} );
} );
In this code,
describe
creates a group composed of several related teststest
is what actually performs a testexpect
wraps our testing subject providing access to a number of “matchers” that lets you make different assertions about its contenttoBe
is one of these matchers. Jest has a lot of matchers included, and there are even others that you can add with a third-party package.
The first test isn’t defining anything for wpJestTests
so its value will be what you defined in globals.js
. Since it’s undefined
, we can’t work so we want to confirm that the function returns without doing anything.
The second test defines wpJestTests
and mocks the document.querySelector
method to return undefined
which is what it would return if it couldn’t find an element in the DOM. In such case, we want to confirm that we’re returning without doing anything, and that our function mocking document.querySelector
was called once.
Mock fetch in Jest tests
The next set of tests begins with
describe( 'Fetch posts', () => {
and inside this, we have another tear down function:
afterAll( () => {
global.fetch.mockClear();
delete global.fetch;
});
unlike afterEach
this will run after all the tests inside this describe
block have run. Since our JavaScript file uses fetch
to load the posts, we need to verify that it was called and it returned what we requested, so we’re going to mock the fetch
function:
global.fetch = jest.fn().mockImplementation(
() => Promise.resolve({
ok: true,
status: 200,
json: () => Promise.resolve( [
{
link: 'test-1',
title: { rendered: 'Post 1' }
},
{
link: 'test-2',
title: { rendered: 'Post 2' }
},
{
link: 'test-3',
title: { rendered: 'Post 3' }
},
{
link: 'test-4',
title: { rendered: 'Post 4' }
},
{
link: 'test-5',
title: { rendered: 'Post 5' }
},
] ),
})
);
We mock the first Promise that resolves to the response, and the second to a JSON representation of the data. This data is similar to what we get from the WP REST API. Given that our code only needs the title and the link, we’re mocking that.
JavaScript integration test with Jest in asynchronous code that uses fetch
We can now write the test using the mocked fetch
. There’s a major difference with this test compared to the other JavaScript unit tests: this is an integration test. Previous tests explored here simply ensured that a component was working well. We checked that if the initial state global variable wasn’t defined, that our component wouldn’t render. This is different because we’re going to test how the entire system works when the initial state variable is defined, thus triggering a data transaction, and finally inserting the post titles with their links in the document.
Right from the beginning, this is different: the anonymous function passed to test
receives a done
parameter. This is actually a function that will be called when we end the test. Jest will wait until done
is called before finishing the test. If done
is never called, the test will fail with a timeout error. That’s interesting for us since we’re testing code involving fetch
, which is an asynchronous function.
test( 'if posts are fetch, inserts them', done => {
wpJestTests = { posts: 5 };
const mockClassListAdd = jest.fn();
const divElem = {
tagName: 'DIV',
classList: { add: mockClassListAdd },
innerHTML: ''
};
const mockQuerySelector = jest.spyOn( document, 'querySelector') .mockImplementation( () => divElem );
expect( wpJestTestsInit() ).toBe( true );
process.nextTick(() => {
expect( mockQuerySelector ).toHaveBeenCalledTimes( 1 );
expect( global.fetch ).toHaveBeenCalledTimes( 1 );
expect( mockClassListAdd ).toHaveBeenCalledTimes( 1 );
expect( divElem.innerHTML.match( /test-[1-5]/g ).length ).toBe( 5 );
done();
});
} );
The global wpJestTests
is defined and our mocked document.querySelector
now returns what resembles an HTML element, with even classList
and its add
child method.
We call wpJestTestsInit
and expect it to end it correctly. Now, since fetch
is asynchronous, we’re going to make use of process.nextTick
from Node.js. The nextTick
in Node.js is the queue that will run after all the events in the current event loop have finished. This is great because all our promises would be resolved then, which is exactly what we need for testing this code that involves fetch
.
The rest are more assertions to ensure the querySelector
found something to work with, that fetch
was indeed called, that a class was added to the list, and that the titles and links of our posts was inserted in the corresponding HTML element. Once all it’s done, we call done
and our asynchronous test ends.
Run your Jest tests
Now you can type
npm run test
and Jest will run the JavaScript unit tests for your WordPress plugin

Conclusion
So Jest is a great and simple solution to write tests that cover the JavaScript code of our WordPress plugins or themes. But there’s more. If we’re writing a React app for our plugin, we might want to make assertions about it. Jest can help up to a certain extent too, and if we need more, we can add Enzyme to our tools and start writing integration tests with it.
Please donate!
If you found this useful, feel free to buy me a coffee ☕️so I can stay awake and create more useful tutorials for you!
$3.00