Here's something you might not expect: when running Rails tests, Webpacker will load the development webpack config instead of the test config by default.

To demonstrate, I'll use some "puts" debugging. Here's a console.log statement in the development config.

// config/webpack/development.js
// ...
console.log('Loading config/webpack/development.js...')

When I run my RSpec tests while logging to STDOUT and RAILS_ENV set to test, the log line is displayed.

$ RAILS_LOG_TO_STDOUT=true RAILS_ENV=test bin/rspec

# ...
[Webpacker] Compiling...
# ...
[Webpacker] Loading config/webpack/development.js...
# ...

If you've noticed this before, there's nothing wrong with your setup; this is the way Webpacker is setup to work out-of-the-box. Nevertheless, this is quite confusing and has lead to a recently reported issue on the Webpacker GitHub repository.

It turns out, even though RAILS_ENV is set to "test", NODE_ENV is set to 'development' (source). The webpack config use is determined by the NODE_ENV, which means, and this is especially pertinent to your Rails system and integration test, the development webpack config is loaded. (You can confirm this by setting NODE_ENV, i.e., NODE_ENV=nonsense bin/webpack).

What gives?

If the "test" NODE_ENV isn't used when I run my Rails tests, what is it good for?

First, let's make it clear that NODE_ENV has no explicit relationship to RAILS_ENV. Setting one of the ENV variables will have no effect on the other.

This is useful for debugging; for example, you can compile your production webpack build locally:

NODE_ENV=production RAILS_ENV=development bin/rails s

Speaking of production, when running rails assets:precompile to compile your build, you don't have to set NODE_ENV to production explicitly because Webpacker does this for you. Otherwise, development is the default.

Another key point the production and development configurations are designed for compiling your JS for a real browser. Though they have different optimization characteristics, they share the same browser-focused Babel config which will transform your nice ES6+ syntax into JavaScript your supported browsers will understand.

So to recap so far, with Webpacker, NODE_ENV determines which webpack config it will use, i.e., config/webpack/{development,test,production}.js and will impact behavior in your Babel config, i.e., babel.config.js.

Testing 1-2-3

This finally brings us to the use case for NODE_ENV=test:

JavaScript unit testing

By this I mean executing tests, written in JavaScript, against your application JavaScript code within a Node.js process.

We're talking Mocha, Jest, Karma, and more.

For some applications, JavaScript unit tests may not add much value, say if you're just doing a little DOM-manipulation with jQuery here and there. However, there may be some benefit to structuring your JavaScript utilities and components into discrete units which can be tested in isolation. And where there are discrete, testable units, there is room for unit testing. That's where JavaScript test runners come in.

The Rails unit testing for asset-pipeline compiled JavaScript is a bit cumbersome; it typically requires a gem, like teaspoon or jasmine-rails, that integrates tightly with the Rails asset pipeline by booting up both Rails and a browser to compile JavaScript and execute tests.

Webpacker opens the door to JavaScript unit test runners that can run in a Node.js process instead of a real browser (typically for speed). Jest, for example, executes tests against a "browser-like" environment called jsdom by default. To support these node.js test runners, Webpacker's default Babel config targets the node.js runtime instead of a browser when NODE_ENV=test; this means Babel will transform your nice ES6+ syntax into JavaScript your current node version will understand assuming you set NODE_ENV=test for running your JavaScript unit tests.

What this means for your application

You can see the potential problem then if you explicitly set NODE_ENV=test for your Rails system and integration tests without considering your Babel config; compiling your JavaScript for the Node.js runtime and loading in the browser may lead to some surprising issues. You can, of course, override this behavior if you really want; at least with this introduction provides some awareness of what you'd be getting yourself into.

You can setup Jest to compile your JavaScript through your webpack configuration. If you follow the general setup instructions for Jest, it's possible to integrate to run your unit tests without webpack at all. Other test runners like karma offer similar options for running with or without webpack.

All this to say: your webpack test config, i.e. config/webpack/test.js, is essentially useless unless your application:

a. uses a Node.js test runner for JavaScript unit test AND configure it to use your webpack config a. overrides the defaults so that the test config is loaded in your Rails tests (just be sure to change Babel behavior)

What about webpacker.yml?

Also, as I've described previously in Understanding webpacker.yml, Webpacker provides a webpack configuration while merging settings declared in config/webpacker.yml from YAML to JavaScript. This file contains settings for production, development, and test environments as do most Rails-y YAML files. Unlike the webpack config, webpacker.yml settings are determined by the current RAILS_ENV.

This means, webpacker.yml test settings are merged into the development webpack config when running your Rails tests.

Wrapping up

Does all of this seem a little confusing? I agree. Here's a breakdown of how webpack configuration maps to RAILS_ENV and NODE_ENV in various contexts.

Context RAILS_ENV webpacker.yml NODE_ENV webpack config
Deployed app production production production config/webpack/production.js
Local server development development development config/webpack/development.js
Rails tests test test development config/webpack/development.js
JS unit tests n/a n/a test config/webpack/test.js if test runner is configured to use webpack

Stated more simply:

RAILS_ENV determines which Webpacker YAML settings are used and NODE_ENV determines which webpack configuration is used.

Whether or not you find the use case for JavaScript unit tests compelling, it helps to know that Webpacker does not make any distinction between your development and test environments beyond the settings in your webpacker.yml; both are local concerns that target the same runtime, i.e., the browser.


If you found this helpful, please consider subscribing to my newsletter to stay tuned for more on upping your "JavaScript on Rails" game.

Discuss it on Twitter · Published on Jul 20, 2020

Need help with webpack on Rails?

My name is Ross Kaffenberger.

Webpacker now ships by default in Rails 6 but there's no need to stress. I'm putting together a course to help you master Webpack on Rails.

I teach full stack web developers about frontend tools and performance, especially at the intersection of JavaScript and Ruby on Rails.

Subscribe to stay in the loop.

    Powered By ConvertKit

    More posts

    A visual guide to Webpacker

    Navigate the world of Webpacker and webpack on Rails with confidence using this collection of mental maps I put together.

    The webpack plugin I can't live without

    In this post, we'll take a look at installing and using the webpack-bundle-analyzer, perhaps the most invaluable webpack plugin, to analyze and debug the output of the webpack build in a Rails project configured to use Webpacker.

    Why does Rails 6 include both Webpacker and Sprockets?

    A new Rails 6 application will install both Webpacker and Sprockets by default. Don't they solve the same problem? This article dives into why Sprockets lives on even though webpack has surpassed most of its features and why you might want to choose one over the other.

    Photo by Green Chameleon