Setting up Rails with Webpack(er), React and Jest.

Setting up Rails with Webpack(er), React and Jest.

An opinionated and straightforward way.

This article has a clear purpose: present a quick and easy way to setup Rails + Webpacker + React + Jest. So if your team is considering, or has already decided, to use React, this is the right article for you. The open source community went through a long journey of features to bring Rails and modern Javascript together. You’ll see that combining React with Rails is easier than ever.

We need to make two concepts very clear before we start. There are two names that look similar, but they are NOT the same. Webpack and Webpacker. Webpack is a javascript bundler and task runner that runs on top of Node.js and thanks to its amazing community, there are a lot of modules to add features to it without or with little specific knowledge and configuration. Webpacker is a ruby gem created by the Rails Core Team to integrate Webpack to the Rails asset pipeline. So that means Webpacker is a way to have the Webpack functionality within a Rails application.

Let’s get started!

Rails setup

In this section we will create a new Rails application to be used in the example. If you already have an Rails app, you can skip to Add Webpacker Gem and React.

Create a new Rails application by running:

$ rails new rails_app --skip-coffee --skip-javascript

A folder called rails_app will be created. It is worth mentioning that you could add a flag --webpack=react and it would skip some steps, but in my experience, most of the projects will, or at least should, add React in a later development stage.

$ cd rails_app

Prepare your database by running these commands:

$ bin/rails db:setup

Let’s create a view to put our React app later on.

$ bin/rails generate controller home
$ touch app/views/home/index.html.erb

Inside config/routes.rb set the root to home#index.

# on config/routes.rb

Rails.application.routes.draw do
  root to: 'home#index'
end

Place any content inside app/views/home/index.html.erb and run $ rails server. Now you can visit localhost:3000 and see your view’s content there. The index will be used to render the React component.

Great! Now we have a running webserver and should be ready to go to the next section.

Add Webpacker Gem and React

At this point we have a view, in which we will render a React component. Now, it is time to add Webpacker which will integrate files generated by Webpack to the Rails assets. As the “Rails doctrine” says convention over configuration, Webpacker add Webpack and configure it so you can start hacking.

Note: If you are doing this in an existing application, Webpacker can operate alongside sprockets. You should discuss with your team whether you should migrate your assets to Webpack or not, according to your context.

Go to Gemfile and add the Webpacker gem.

# on Gemfile

gem 'webpacker'

Run the command to install:

$ bundle install

With the gem installed we need to run the task to setup React and Webpack with Rails. To do that, you need to have Yarn installed. You can follow this guide.

$ bin/rails webpacker:install
$ bin/rails webpacker:install:react

Go to app/views/home/index.html.erb and add the hello_react import.

/* anywhere in app/views/index.html.erb */

<%= javascript_pack_tag 'hello_react' %>

Go to localhost:3000 and it should show Hello React!

All right, React and Webpack are all set and ready to go! However, we are not fully done yet. The reason behind React existing in the first place, is that UI can be very complex and even though React(and other frameworks) tries to simplify it, we still have to create a lot of logic in an React application. To ensure that our components are working correctly we need to test them!

Add Jest

In this section we will add Jest, a test framework, created by the Facebook team, the same team behind React. You can use other test library, but I chose Jest because of its syntax that relates to RSpec, works with all major javascript libraries, provides a good bundle of utilities, and the setup is simpler.

We need to add Jest with Yarn because now we are using the javascript ecosystem.

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

Jest has many configurations, but we want to keep it as short as possible, since the defaults are pretty good. But there are two items that you need do adapt in order for it to work nicely with Rails.

The moduleDirectories configuration indicates where to find modules. I think it’s useful to add the Rails javascript folder to the moduleDirectories configuration because it will be easier to import the files to be tested. For example, use the following directory structures:

app
| - javascript
| | - packs
| | | - index.js
...
spec
| - javascript
| | - packs
| | | - index.spec.js

Without moduleDirectories configuration, to import app/javascript/packs/index.js in spec/javascript/packs/index.spec.js, you’ll need to configure the import using a relative path that will look like this: ../../../app/javascript/packs/index.js. It’s very painful to maintain relative paths in a long-term project, especially when you need to rename directories and move files.
As it is suggested by Kyle Fox here, add app/javascript to the modules directory of Jest. Then you can use only packs/index.js to import from index.spec.js.

The root configuration indicates the directory of your tests file.
Jest will look for files to run and will match the files with *spec.js or *test.js, so it is important to set the root for Jest otherwise it will try to run test.js on config/webpack as a test. I will use spec/javascript because I like the RSpec folder organization, but you can change to any folder you want.

Add to your package.json the custom Jest configuration.

/* On your package.json */

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

On your .babelrc, add the es2015 preset to ensure your ES6 is working fine throughout you app and your tests.

/* On your .babelrc */

{
  "presets": [
    "es2015",
    ...
  ]
}

You can try run yarn jest, but it will fail. You don’t have any test yet, so, let’s create one. We’re going to test a React component generated by Webpacker, we will talk about it soon. We need to add the enzyme library to render React components in the test environment. We need to add an adapter together with Enzyme to render our React components. Run the following command:

$ yarn add --dev enzyme enzyme-adapter-react-16

Please, notice that the adapter’s version depends on the version of react that you are using.

Now, to setup enzyme and its adapter we will create a file to setup the tests, and you can add other configurations like setup Mocha and Chai.

$ touch spec/javascript/setupTests.js

Inside setupTests.js put:

/* On spec/javascript/setupTests.js */

import Enzyme from 'enzyme'
import EnzymeAdapter from 'enzyme-adapter-react-16'

Enzyme.configure({ adapter: new EnzymeAdapter() })

Add the setup path to Jest:

/* On your .babelrc */

"jest": {
  "roots": [
    "spec/javascript"
  ],
  "moduleDirectories": [
    "node_modules",
    "app/javascript"
  ],
  "setupTestFrameworkScriptFile": "./spec/javascript/setupTests.js"
}

Ok, now we have Jest ready to go, with enzyme to render our components. We can go to the next section to create tests for our component.

Testing with Jest + Enzyme

Now it is time to dive into Jest tests and ensure that the component behave like we expect. We will import the hello_react component and test it by rendering and checking its content.

The component created by Webpacker looks something like this:


/* On app/javascript/packs/hello_react.jsx */ import React from 'react' import ReactDOM from 'react-dom' import PropTypes from 'prop-types' const Hello = props => (
Hello {props.name}!
) Hello.defaultProps = { name: 'David' } Hello.propTypes = { name: PropTypes.string } document.addEventListener('DOMContentLoaded', () => { ReactDOM.render( <Hello name="React" />, document.body.appendChild(document.createElement('div')), ) })

Add at the end of the file the following line to export the component. We can only test exported components in our Jest test files.

/* Add at the end of app/javascript/packs/hello_react.jsx */

export default Hello

I won’t go in depth on how React works, let’s play along with that component. The component should render a div with Hello {name}! inside. So, lets create a test for that.

$ touch spec/javascript/packs/hello_react.spec.js

Open the test file with your favorite text editor. We need to import the component, Enzyme, and React in order to render the component.

/* On spec/javascript/packs/hello_react.spec.js */

import React from 'react'
import { shallow } from 'enzyme'
import HelloReact from 'packs/hello_react'

Please notice that we have imported only shallow from enzyme. shallow improves the test performance by only rendering the component and its children, and it prevents rendering the entire DOM tree. However, you might need other rendering types, in this case, check the Enzyme’s documentation.

Let’s describe the component and create an expectation of having Hello David! or any other name we pass to the HelloReact component.

/* On spec/javascript/packs/hello_react.spec.js */

describe('HelloReact component', () => {
  describe('when a name is given as a prop', () => {
    it('render Hello Caique!', () => {
      expect(shallow().text()).toBe('Hello Caique!')
    })
  })

  describe('when no name is given', () => {
    it('render Hello David!', () => {
      expect(shallow().text()).toBe('Hello David!')
    })
  })
})

Next run $ yarn test to see if the tests were successful and that’s it.

Now you have a full Rails + React + tests setup. To extend that setup you could add Redux to manage state or Mocha/Chai to test, but either way, from now on it is just a matter of following documentation to add the frameworks as any Yarn + Webpack setup, then Webpacker should run without hassle. If you have any question about this tutorial, feel free to leave a comment below.

Comments are closed.