Skip to main content
Headshot of Damon Bauer Damon Bauer

Testing Stimulus With Jest

I've been introducing Stimulus to a Rails app that uses Jest for testing JavaScript. After some research, I've found a pretty decent setup that allows me to load up a Stimulus controller to writes tests. If you need to do the same, here are some things you'll need, as well as some nice-to-haves.

MutationObserver

Stimulus relies on MutationObserver, but Jest (which uses jsdom under the hood) doesn't support it, so you'll need to fake it. Thankfully, there's a mutationobserver-shim that handles it for you. Install it by running yarn i mutationobserver-shim --dev.

You'll also need a Jest setup script. I like to make a setup-jest.js file in the root of my project. Inside, import the shim:

// ./setup-jest.js
import 'mutationobserver-shim';

Make sure you configure Jest to look for this file. I like to make a jest-config.js file in the root of my project; this file holds all the configuration I need for Jest. Set the setupFilesAfterEnv option to look for the setup file:

// ./jest-config.js
// other config...
setupFilesAfterEnv: [
  '<rootDir>/setup-jest.js'
]

Writing A Test

Now, it's just a matter of including your Stimulus controller in your test. To do so, you'll need to import and "start" Stimulus, and register your controller that you want to test:

import { Application } from 'stimulus';
import HelloWorldController from '../../controllers/hello_world_controller';

const startStimulus = () => {
  const application = Application.start();
  application.register('hello-world', HelloWorldController);
};

describe('the file under test', () => {
  beforeEach(() => {
    startStimulus();
    document.body.innerHtml = `
      <form data-controller="hello-world">
        <input type="text" data-testid="Foo">
        <button name="button" type="submit" disabled="disabled">Verify</button>
      </form>
    `
  });

  describe('a scenario', () => {
    it('tests the scenario', () => {
      // ...
    });
  });
});

Nice To Haves

dom-testing-library

I like to use dom-testing-library to make accessing & manipulating the DOM easier in tests. When writing tests for Stimulus controllers, I've found that getByTestId and waitForDomChange are 2 especially helpful methods.

mountDOM / cleanupDOM

I wrote a few quick helper methods that make it easy to add & remove markup from the DOM. These live in a utils.js file in app/javascript/__tests__/utils.js.

// app/javascript/__tests__/utils.js

/**
 * Inserts a string of HTML into the DOM.
 * @param {string} htmlString - The HTML to insert.
 * @returns {HTMLElement} The newly inserted DOM element.
 */
const mountDOM = (htmlString = '') => {
  const div = document.createElement('div');
  div.innerHTML = htmlString;
  document.body.appendChild(div);

  return div;
};

const cleanupDOM = () => {
  document.body.innerHTML = '';
};

export {
  cleanupDOM,
  mountDOM
};

Typically, a test I write will use these methods like so:

let container = null;

describe('the file under test', () => {
  afterEach(() => {
    cleanupDOM();
  });

  describe('a scenario', () => {
    it('tests the scenario', () => {
      container = mountDOM(`
        <form data-controller="hello-world">
          <input type="text" data-testid="Foo">
          <button name="button" type="submit" disabled="disabled">Verify</button>
        </form>
      `);

      // Continue with the test...
    });
  });
Tags: