React testing setup with rails/webpacker React testing setup with rails/webpacker reactjs reactjs

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

  1. 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
  2. Until Rails 5.1 ships, we need Webpacker.

    # Gemfilegem 'webpacker', git: 'https://github.com/rails/webpacker.git'

    Then in your shell

    $ bundle install
  3. Set up Webpack and React using Webpacker

    $ rails webpacker:install && rails webpacker:install:react
  4. Install Jest, and Jest related packages

    $ bin/yarn add jest babel-jest babel-polyfill react-test-renderer --dev
  5. 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"  ]}
  6. 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 with babel-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 to node_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 the babel-loader configuration in config/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!