React testing setup with rails/webpacker
NOTE: The process I went through to answer this question is not longer necessary, as the Rails team has moved JavaScript dependencies back into the root of the project by default.
This answer is a two-parter. First, I'll explain how, after much blood, sweat and tears, I arrived at my current Webpacker + React + Jest test environment. Second, I'll dive into why all the expenditure of the bodily fluids.
Part 1: Implementation
Rails. I generated a new Rails app, skipping all of the things that I'm not likely to need.
rails new rails-react --skip-action-mailer --skip-active-record --skip-action-cable --skip-javascript --skip-turbolinks
Until Rails 5.1 ships, we need Webpacker.
# Gemfilegem 'webpacker', git: 'https://github.com/rails/webpacker.git'
Then in your shell
$ bundle install
Set up Webpack and React using Webpacker
$ rails webpacker:install && rails webpacker:install:react
Install Jest, and Jest related packages
$ bin/yarn add jest babel-jest babel-polyfill react-test-renderer --dev
Configure Jest to play nice with Webpacker. This article on configuring Jest for Webpack is very helpful. According to the docs "
<rootDir>
is a special token that gets replaced by Jest with the root of your project. Most of the time this will be the folder where your package.json is located". Crucially for us this is/vendor
and not/
as it would be in a conventional JavaScript project.// vendor/package.json"jest": { // The name of this directive will soon change to "roots" // https://github.com/facebook/jest/issues/2600 // This points Jest at Webpacker's default JS source directory "testPathDirs": [ "<rootDir>/../app/javascript/packs" ], // This ensures that node_modules are resolved properly // By default, Jest looks in "/" for this, not "vendor/" "moduleDirectories": [ "<rootDir>/node_modules" ], "moduleFileExtensions": [ "js", "jsx" ]}
Babel. This one caused me the most headache. Babel doesn't support dynamic configuration (although it is currently being considered). Additionally, the way Babel looks for configuration does not help us. "Babel will look for a .babelrc in the current directory of the file being transpiled. If one does not exist, it will travel up the directory tree until it finds either a .babelrc, or a package.json with a "babel": {} hash within."
The way Babel looks for presets is also brittle. You'll note that the babel-handbook documentation for creating presets include the step "simply publish this to npm and you can use it like you would any preset". If you want Babel to actually find a preset in the conventional way, there is no alternative to using an npm package inside of the
node_modules
directory with a name starting withbabel-preset
.This means that we need to create a
.babelrc
file in the Rails root, and then use the full path to each plugin that we need, relative to that.babelrc
file, rather than the short-hand that you'll be familiar with if you've used Babel in the past (e.g."presets": ["react"]
refers tonode_modules/babel-preset-react
). My.babelrc
, following the Jest docs on using Webpack 2 and using babel looks like this:// .babelrc{ "presets": [ "./vendor/node_modules/babel-preset-react", "./vendor/node_modules/babel-preset-es2015" ], "env": { "test": { "plugins": [ "./vendor/node_modules/babel-plugin-transform-es2015-modules-commonjs" ] } }}
Remove the
options
key under thebabel-loader
configuration inconfig/webpack/shared.js
so that Webpack uses this configuration file, too.
That brings us up to the present moment. I implemented the basic sum (vanilla JS) and Link (React) tests in the Jest docs and run them by executing npm run test
in the /vendor
directory. You can see the fruits of my labors here: https://github.com/pstjean/webpacker-jest
Part 2. Why?
This is not an easy thing to do right now. It should be. The root of our problems is the unconventional directory structure imposed by Webpacker.
The original reasoning for moving all of our Yarn and Webpack files under /vendor
is that, according to DHH, having these things in the project root as is convention is not "a beautiful way to cohabit app-focused JS within a Rails app". You can see the discussion on the commit implementing this here. User lemonmade's comment on this commit foretells our current frustrations: "It will make actually configuring any JavaScript tooling extremely difficult, and causes it to behave differently than it would in any other project that uses npm packages."
Downthread, DHH says "Appreciate the input and will keep it under advisement as we go forward. This isn't set in stone, but for now we're going to try to make this work." In my opinion, this isn't tenable, and hopefully the Rails team will realize that before 5.1 launches with this in core. There is currently a very promising pull request to remedy this issue in the webpacker project. Let's hope it gets some traction!