How to write JavaScript unit tests using Jest for your WordPress plugins

Easy JavaScript unit tests in WordPress with Jest

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

Overview of the PHP side

Set up Jest tests

Mock styles import for Jest tests

Set up globals for Jest

Overview of the JavaScript file

Writing tests with Jest, finally!

Mock fetch in Jest tests

JavaScript integration test with Jest in asynchronous code that uses fetch

Run your Jest tests

Conclusion

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 and it or test
  • you can make assertions with expect and toBe, 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 to true.
  • 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 like wp_add_inline_script or wp_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 and globals.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 tests
  • test is what actually performs a test
  • expect wraps our testing subject providing access to a number of “matchers” that lets you make different assertions about its content
  • toBe 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

Jest tests running for the JavaScript of a 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

Leave a Reply