How to setup JavaScript testing in Rails 5.1 with Webpacker and Jest

Rails 5.1 ships with Webpacker, which enables dinosaurs like myself to wade tepidly into the buzzing JavaScript — err, ECMAScript— tar pit that cool millennials are swimming in these days.

There doesn’t seem to be much documentation on how to setup testing with Webpacker, so I figured I’d chronicle how I managed to eventually get it working. I decided to try Jest for no reason in particular, even though I’m (gasp!) not using React.

Disclaimer: I’m definitely a newb when it comes to this stuff. It all feels a bit like a Rube Goldberg machine constructed from JSON and dotfiles. So I won’t be surprised (or offended) when people tell me there’s a better way of achieving something I’ve described here. In fact, I’d love to know how this setup could be improved. 🙌

1. Install Jest

First, install Jest by running this from the root of your Rails project.

yarn add --dev jest

A common practice it to setup a script that executes your test commands. Add this to your package.json file:

"scripts": {
"test": "jest"
},

and you’ll be able to run your tests with yarn test. Of course, at this point you could simply run yarn jest, but that’s just silly. Why would you do that. 🤣

2. Tell Jest where to find your tests

One thing that’s nice about Jest is how aggressively it looks for test files in your project. For example, Jest will execute all files matching *.spec.js or *.test.js in your project … which means config/webpack/test.js will be run as a test, causing Jest to report a failure.

My OCD brain can’t handle this kind of loosey-goosey matching, so I used the roots config option to tell Jest to run only test files found in the spec/javascript directory. If you’re like me, add this to package.json:

"jest": {
"roots": [
"spec/javascript"
]
},

You can verify the setup so far by stubbing in a test file and running it. Create spec/javascript/sum.spec.js with a simple test:

test('1 + 1 equals 2', () => {
expect(1 + 1).toBe(2);
});

Now, run yarn test and you should see some green output and feel that familiar, satisfying Pavlovian response that only TDD can provide. 👏

Update: I discovered that adding app/javascript to roots enabled me to use the --watchAll flag, which runs tests whenever files change (like Guard). Combine with the --notify flag for added zen 👍 Still trying to figure out how to get --watch to work … 🤔

3. Setup Babel

I’m also using cool stuff like import, which didn’t seem to work out of the box. Running my specs raised this error:

SyntaxError: Unexpected token import

This surprised me — it’s likely I’ve configured something wrong. But I eventually got Babel working by installing a few additional packages:

yarn add --dev babel-jest babel-preset-es2015

and adding the"es2015" preset (bolded) to my .babelrc file:

{
"presets": ["es2015",
["env", {
"modules": false,
"targets": {
"browsers": "> 1%",
"uglify": true
},
"useBuiltIns": true
}]
],
"plugins": [
"syntax-dynamic-import",
["transform-class-properties", { "spec": true }]
]
}

4. Tell Jest where your modules live

I didn’t like the idea of typing out the full path to my modules from within my specs. The moduleDirectories setting can be used to tell Jest where to look for modules, so I pointed it to my source files in app/javascript. My Jest configuration in package.json ended up looking like this:

"jest": {
"roots": [
"spec/javascript"
],
"moduleDirectories": [
"node_modules",
"app/javascript"
]

},

Note: the value passed to moduleDirectories overrides the default, so you must also include the node_modules directory.

The final result is pretty sweet, and very closely matches my RSpec setup. For example, you can have a source file app/javascript/Dinosaur.js:

export default class Dinosaur {
get isExtinct() {
return true;
}
}

and a matching test file spec/javascript/Dinosaur.spec.js:

import Dinosaur from 'Dinosaur';
test('Dinosaurs are extinct', () => {
expect((new Dinosaur).isExtinct).toBeTruthy();
});

and yarn test Just Works™ 🎉

BONUS ROUND: Setup Codeship to run Jest tests

I use Codeship for continuous integration and thought it would be nifty to have it run my Jest suite in addition to my RSpec suite. Turns out it was super easy.

I added this to my Setup Commands:

nvm install 8.4.0
yarn install

And this to my Test Commands:

yarn test

Neato 👍

Summary

As mentioned, I’ve probably done all this in a roundabout way. I would love feedback or suggestions from folks who actually know what they’re doing! Leave a comment or tweet @kylefox.